From 5322c99183c938642500615477dbb2e85ee74ae2 Mon Sep 17 00:00:00 2001 From: booploops <49113086+booploops@users.noreply.github.com> Date: Wed, 2 Mar 2022 17:42:43 -0800 Subject: [PATCH 01/37] big ass front end cleanup --- src/renderer/audio/audio.js | 27 +- src/renderer/index.js | 4522 +---------------- src/renderer/{js => lib}/bootbox.min.js | 0 src/renderer/{js => lib}/bootstrap.min.js | 0 src/renderer/{js => lib}/fast-plural-rules.js | 0 .../{js => lib}/jquery-3.2.1.slim.min.js | 0 src/renderer/{js => lib}/less.js | 0 src/renderer/{js => lib}/notyf.min.js | 0 src/renderer/{js => lib}/popper.min.js | 0 src/renderer/lib/resonance-audio.min.js | 181 + src/renderer/{js => lib}/showdown.min.js | 0 src/renderer/{js => lib}/smoothscroll.js | 0 src/renderer/{js => lib}/sortable.min.js | 0 src/renderer/{js => lib}/velocity.min.js | 0 src/renderer/{js => lib}/vue-horizontal.js | 0 .../{js => lib}/vue-observe-visibility.min.js | 0 src/renderer/{js => lib}/vue.dev.js | 0 src/renderer/{js => lib}/vue.js | 0 .../{js => lib}/vuedraggable.umd.min.js | 0 src/renderer/{js => lib}/vuex.min.js | 0 src/renderer/main/app.js | 28 + src/renderer/main/cidercache.js | 24 + src/renderer/main/ciderfrontapi.js | 16 + src/renderer/main/events.js | 74 + src/renderer/main/gamepad.js | 326 ++ src/renderer/main/vueapp.js | 4070 +++++++++++++++ src/renderer/main/vuex-store.js | 20 + .../wsapi_interop.js} | 16 +- src/renderer/views/main.ejs | 45 +- 29 files changed, 4787 insertions(+), 4562 deletions(-) rename src/renderer/{js => lib}/bootbox.min.js (100%) rename src/renderer/{js => lib}/bootstrap.min.js (100%) rename src/renderer/{js => lib}/fast-plural-rules.js (100%) rename src/renderer/{js => lib}/jquery-3.2.1.slim.min.js (100%) rename src/renderer/{js => lib}/less.js (100%) rename src/renderer/{js => lib}/notyf.min.js (100%) rename src/renderer/{js => lib}/popper.min.js (100%) create mode 100644 src/renderer/lib/resonance-audio.min.js rename src/renderer/{js => lib}/showdown.min.js (100%) rename src/renderer/{js => lib}/smoothscroll.js (100%) rename src/renderer/{js => lib}/sortable.min.js (100%) rename src/renderer/{js => lib}/velocity.min.js (100%) rename src/renderer/{js => lib}/vue-horizontal.js (100%) rename src/renderer/{js => lib}/vue-observe-visibility.min.js (100%) rename src/renderer/{js => lib}/vue.dev.js (100%) rename src/renderer/{js => lib}/vue.js (100%) rename src/renderer/{js => lib}/vuedraggable.umd.min.js (100%) rename src/renderer/{js => lib}/vuex.min.js (100%) create mode 100644 src/renderer/main/app.js create mode 100644 src/renderer/main/cidercache.js create mode 100644 src/renderer/main/ciderfrontapi.js create mode 100644 src/renderer/main/events.js create mode 100644 src/renderer/main/gamepad.js create mode 100644 src/renderer/main/vueapp.js create mode 100644 src/renderer/main/vuex-store.js rename src/renderer/{js/WSAPI_Interop.js => main/wsapi_interop.js} (92%) diff --git a/src/renderer/audio/audio.js b/src/renderer/audio/audio.js index bb604e45..8f24d107 100644 --- a/src/renderer/audio/audio.js +++ b/src/renderer/audio/audio.js @@ -1,4 +1,4 @@ -var CiderAudio = { +const CiderAudio = { context : null, source : null, audioNodes : { @@ -304,7 +304,7 @@ var CiderAudio = { CiderAudio.audioNodes.analogWarmth = [] - for (i = 0; i < WARMTH_FREQUENCIES.length; i++) { + for (let i = 0; i < WARMTH_FREQUENCIES.length; i++) { CiderAudio.audioNodes.analogWarmth[i] = CiderAudio.context.createBiquadFilter(); CiderAudio.audioNodes.analogWarmth[i].type = 'peaking'; // 'peaking'; CiderAudio.audioNodes.analogWarmth[i].frequency.value = WARMTH_FREQUENCIES[i]; @@ -312,7 +312,7 @@ var CiderAudio = { CiderAudio.audioNodes.analogWarmth[i].gain.value = WARMTH_GAIN[i] * app.cfg.audio.maikiwiAudio.analogWarmth_value; } - for (i = 1; i < WARMTH_FREQUENCIES.length; i ++) { + for (let i = 1; i < WARMTH_FREQUENCIES.length; i ++) { CiderAudio.audioNodes.analogWarmth[i-1].connect(CiderAudio.audioNodes.analogWarmth[i]); } @@ -345,7 +345,7 @@ var CiderAudio = { CiderAudio.audioNodes.llpw = [] if (app.cfg.audio.maikiwiAudio.ciderPPE_value === 0.55) { - for (i = 0; i < c_LLPW_FREQUENCIES.length; i++) { + for (let i = 0; i < c_LLPW_FREQUENCIES.length; i++) { CiderAudio.audioNodes.llpw[i] = CiderAudio.context.createBiquadFilter(); CiderAudio.audioNodes.llpw[i].type = 'peaking'; // 'peaking'; CiderAudio.audioNodes.llpw[i].frequency.value = c_LLPW_FREQUENCIES[i]; @@ -354,7 +354,7 @@ var CiderAudio = { } - for (i = 1; i < c_LLPW_FREQUENCIES.length; i ++) { + for (let i = 1; i < c_LLPW_FREQUENCIES.length; i ++) { CiderAudio.audioNodes.llpw[i-1].connect(CiderAudio.audioNodes.llpw[i]); } @@ -373,7 +373,7 @@ var CiderAudio = { } else if (app.cfg.audio.maikiwiAudio.ciderPPE_value === 0.5) { - for (i = 0; i < LLPW_FREQUENCIES.length; i++) { + for (let i = 0; i < LLPW_FREQUENCIES.length; i++) { CiderAudio.audioNodes.llpw[i] = CiderAudio.context.createBiquadFilter(); CiderAudio.audioNodes.llpw[i].type = 'peaking'; // 'peaking'; CiderAudio.audioNodes.llpw[i].frequency.value = LLPW_FREQUENCIES[i]; @@ -382,7 +382,7 @@ var CiderAudio = { } - for (i = 1; i < LLPW_FREQUENCIES.length; i ++) { + for (let i = 1; i < LLPW_FREQUENCIES.length; i ++) { CiderAudio.audioNodes.llpw[i-1].connect(CiderAudio.audioNodes.llpw[i]); } @@ -408,7 +408,7 @@ var CiderAudio = { let VIBRANTBASSQ = app.cfg.audio.maikiwiAudio.vibrantBass.Q; CiderAudio.audioNodes.vibrantbassNode = [] - for (i = 0; i < VIBRANTBASSBANDS.length; i++) { + for (let i = 0; i < VIBRANTBASSBANDS.length; i++) { CiderAudio.audioNodes.vibrantbassNode[i] = CiderAudio.context.createBiquadFilter(); CiderAudio.audioNodes.vibrantbassNode[i].type = 'peaking'; // 'peaking'; CiderAudio.audioNodes.vibrantbassNode[i].frequency.value = VIBRANTBASSBANDS[i]; @@ -416,7 +416,7 @@ var CiderAudio = { CiderAudio.audioNodes.vibrantbassNode[i].gain.value = VIBRANTBASSGAIN[i] * app.cfg.audio.maikiwiAudio.vibrantBass.multiplier; } - for (i = 1; i < VIBRANTBASSBANDS.length; i ++) { + for (let i = 1; i < VIBRANTBASSBANDS.length; i ++) { CiderAudio.audioNodes.vibrantbassNode[i-1].connect(CiderAudio.audioNodes.vibrantbassNode[i]); } @@ -683,7 +683,7 @@ var CiderAudio = { CiderAudio.audioNodes.audioBands = []; - for (i = 0; i < BANDS.length; i++) { + for (let i = 0; i < BANDS.length; i++) { CiderAudio.audioNodes.audioBands[i] = CiderAudio.context.createBiquadFilter(); CiderAudio.audioNodes.audioBands[i].type = 'peaking'; // 'peaking'; CiderAudio.audioNodes.audioBands[i].frequency.value = BANDS[i]; @@ -694,14 +694,11 @@ var CiderAudio = { // Dynamic-ish loading CiderAudio.hierarchical_loading(); - for (i = 1; i < BANDS.length; i ++) { + for (let i = 1; i < BANDS.length; i ++) { CiderAudio.audioNodes.audioBands[i-1].connect(CiderAudio.audioNodes.audioBands[i]); } CiderAudio.audioNodes.audioBands[BANDS.length-1].connect(CiderAudio.context.destination); } } -if (app.cfg.advanced.AudioContext){ - CiderAudio.init() - -} \ No newline at end of file +export {CiderAudio} \ No newline at end of file diff --git a/src/renderer/index.js b/src/renderer/index.js index 538a4c6b..eb5d268e 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -1,29 +1,3 @@ -Vue.use(VueHorizontal); -Vue.use(VueObserveVisibility); - -const CiderCache = { - async getCache(file) { - let cache = await ipcRenderer.sendSync("get-cache", file) - if (isJson(cache)) { - cache = JSON.parse(cache) - if (Object.keys(cache).length === 0) { - cache = false - } - } else { - cache = false - } - return cache - }, - async putCache(file, data) { - console.log(`Caching ${file}`) - ipcRenderer.invoke("put-cache", { - file: file, - data: JSON.stringify(data) - }) - return true - } -} - var notyf = new Notyf(); const MusicKitObjects = { @@ -39,20 +13,7 @@ const MusicKitObjects = { } } -const CiderFrontAPI = { - Objects: { - MenuEntry: function () { - this.id = "" - this.name = "" - this.onClick = () => { - } - } - }, - AddMenuEntry(entry) { - app.pluginMenuEntries.push(entry) - app.pluginInstalled = true - } -} + const MusicKitTools = { getHeader() { @@ -70,4412 +31,6 @@ Array.prototype.limit = function (n) { return this.slice(0, n); }; -const store = new Vuex.Store({ - state: { - library: { - // songs: ipcRenderer.sendSync("get-library-songs"), - // albums: ipcRenderer.sendSync("get-library-albums"), - // recentlyAdded: ipcRenderer.sendSync("get-library-recentlyAdded"), - // playlists: ipcRenderer.sendSync("get-library-playlists") - }, - artwork: { - playerLCD: "" - } - }, - mutations: { - setLCDArtwork(state, artwork) { - state.artwork.playerLCD = artwork - } - } -}) -const app = new Vue({ - el: "#app", - store: store, - data: { - version: ipcRenderer.sendSync("get-version"), - appMode: "player", - ipcRenderer: ipcRenderer, - cfg: ipcRenderer.sendSync("getStore"), - isDev: ipcRenderer.sendSync("is-dev"), - drawertest: false, - platform: "", - mk: {}, - quickPlayQuery: "", - pluginInstalled: false, - pluginMenuEntries: [], - lz: ipcRenderer.sendSync("get-i18n", "en_US"), - lzListing: ipcRenderer.sendSync("get-i18n-listing"), - search: { - term: "", - hints: [], - showHints: false, - results: {}, - resultsSocial: {}, - limit: 10 - }, - fullscreenLyrics: false, - playerLCD: { - playbackDuration: 0, - desiredDuration: 0, - userInteraction: false - }, - drawer: { - open: false, - panel: "" - }, - browsepage: [], - listennow: [], - madeforyou: [], - radio: { - personal: [] - }, - mklang: 'en', - webview: { - url: "", - title: "", - loading: false - }, - showingPlaylist: [], - appleCurator: [], - artistPage: { - data: {}, - }, - library: { - backgroundNotification: { - show: false, - message: "", - total: 0, - progress: 0 - }, - songs: { - sortingOptions: { - "albumName": "0", - "artistName": "0", - "name": "0", - "genre": "0", - "releaseDate": "0", - "durationInMillis": "0", - "dateAdded": "0" - }, - sorting: "name", - sortOrder: "asc", - listing: [], - meta: { total: 0, progress: 0 }, - search: "", - displayListing: [], - downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library - }, - albums: { - sortingOptions: { - "albumName": "0", - "artistName": "0", - "name": "0", - "genre": "0" - }, - viewAs: 'covers', - sorting: ["dateAdded", "name"], // [0] = recentlyadded page, [1] = albums page - sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page - listing: [], - meta: { total: 0, progress: 0 }, - search: "", - displayListing: [], - downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library - }, - artists: { - sortingOptions: { - "artistName": "0", - "name": "0", - "genre": "0", - "releaseDate": "0" - }, - viewAs: 'covers', - sorting: ["dateAdded", "name"], // [0] = recentlyadded page, [1] = albums page - sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page - listing: [], - meta: { total: 0, progress: 0 }, - search: "", - displayListing: [], - downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library - }, - }, - playlists: { - listing: [], - details: {}, - loadingState: 0, // 0 loading, 1 loaded, 2 error - id: "", - trackMapping: {} - }, - webremoteurl: "", - webremoteqr: "", - mxmtoken: "", - mkIsReady: false, - playerReady: false, - animateBackground: false, - currentArtUrl: '', - currentArtUrlRaw: '', - lyricon: false, - currentTrackID: '', - currentTrackIDBG: '', - lyrics: [], - currentLyricsLine: 0, - lyriccurrenttime: 0, - richlyrics: [], - lyricsMediaItem: {}, - lyricsDebug: { - current: 0, - start: 0, - end: 0 - }, - v3: { - requestBody: { - platform: "web" - } - }, - tmpHeight: '', - tmpWidth: '', - tmpVar: [], - notification: false, - chrome: { - appliedTheme: { - location: "", - info: {} - }, - desiredPageTransition: "wpfade_transform", - hideUserInfo: ipcRenderer.sendSync("is-dev") || false, - artworkReady: false, - userinfo: { - "id": "", - "attributes": { - "name": "Cider User", - "handle": "CiderUser", - "artwork": { "url": "./assets/logocut.png" } - } - }, - menuOpened: false, - maximized: false, - drawerOpened: false, - drawerState: "queue", - topChromeVisible: true, - progresshover: false, - windowControlPosition: "right", - contentAreaScrolling: true, - showCursor: false - }, - collectionList: { - response: {}, - title: "", - type: "" - }, - prevButtonBackIndicator: false, - currentSongInfo: {}, - page: "", - pageHistory: [], - songstest: false, - hangtimer: null, - selectedMediaItems: [], - routes: ["browse", "listen_now", "radio"], - musicBaseUrl: "https://api.music.apple.com/", - modals: { - addToPlaylist: false, - spatialProperties: false, - qrcode: false, - equalizer: false, - audioSettings: false, - pluginMenu: false, - audioControls: false, - showPlaylist: false, - castMenu: false - }, - socialBadges: { - badgeMap: {}, - version: "", - mediaItems: [], - mediaItemDLState: 0 // 0 = not started, 1 = in progress, 2 = complete - }, - menuPanel: { - visible: false, - event: null, - content: { - name: "", - items: {}, - headerItems: {} - } - }, - pauseButtonTimer: null, - activeCasts: [] - }, - watch: { - cfg: { - handler: function (val, oldVal) { - console.log(`cfg changed from ${oldVal} to ${val}`); - ipcRenderer.send("setStore", val); - }, - deep: true - }, - page: () => { - document.getElementById("app-content").scrollTo(0, 0); - app.resetState() - }, - showingPlaylist: () => { - if (!app.modals.showPlaylist) { - document.getElementById("app-content").scrollTo(0, 0); - app.resetState() - } - }, - artistPage: () => { - document.getElementById("app-content").scrollTo(0, 0); - app.resetState() - } - }, - methods: { - simulateController() { - this.chrome.showCursor = true - let cursorPos = [0, 0]; - let intTabIndex = 0 - let self = this - const cursorSpeedPvt = 8 - const cursorSize = 16 - let scrollSpeed = 8 - let buttonPressDelay = 500 - let stickDeadZone = 0.2 - let scrollGroup = null - let scrollGroupY = null - let elementFocusEnabled = true - - let cursorSpeed = cursorSpeedPvt - - let lastButtonPress = { - - } - - var sounds = { - Confirm: new Audio("./sounds/confirm.ogg"), - Menu: new Audio("./sounds/btn1.ogg"), - Hover: new Audio("./sounds/hover.ogg") - } - - let element = document.elementFromPoint(0, 0) - let elementType = 0 - - function appLoop() { - var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []); - if (!gamepads) { - return; - } - - var gp = gamepads[0]; - - // LEFT STICK - if (gp.axes[0] > stickDeadZone) { - cursorPos[0] += (gp.axes[0] * cursorSpeed) - } else if (gp.axes[0] < -stickDeadZone) { - cursorPos[0] += (gp.axes[0] * cursorSpeed) - } - - if (gp.axes[1] > stickDeadZone) { - cursorPos[1] += (gp.axes[1] * cursorSpeed) - } else if (gp.axes[1] < -stickDeadZone) { - cursorPos[1] += (gp.axes[1] * cursorSpeed) - } - - if (cursorPos[0] < cursorSize) { - cursorPos[0] = cursorSize - } - if (cursorPos[1] < cursorSize) { - cursorPos[1] = cursorSize - } - if (cursorPos[0] > window.innerWidth - cursorSize) { - cursorPos[0] = window.innerWidth - cursorSize - } - if (cursorPos[1] > window.innerHeight - cursorSize) { - cursorPos[1] = window.innerHeight - cursorSize - } - - - // RIGHT STICK. - if (scrollGroupY) { - if (gp.axes[3] > stickDeadZone) { - $(scrollGroupY).scrollTop($(scrollGroupY).scrollTop() + (gp.axes[3] * scrollSpeed)) - elementFocusEnabled = false - } else if (gp.axes[3] < -stickDeadZone) { - $(scrollGroupY).scrollTop($(scrollGroupY).scrollTop() + (gp.axes[3] * scrollSpeed)) - elementFocusEnabled = false - } else { - elementFocusEnabled = true - } - } - - - - if (scrollGroup) { - if (gp.axes[2] > stickDeadZone) { - $(scrollGroup).scrollLeft($(scrollGroup).scrollLeft() + (gp.axes[2] * scrollSpeed)) - elementFocusEnabled = false - } else if (gp.axes[2] < -stickDeadZone) { - $(scrollGroup).scrollLeft($(scrollGroup).scrollLeft() + (gp.axes[2] * scrollSpeed)) - elementFocusEnabled = false - } else { - elementFocusEnabled = true - } - } - - - $(".cursor").css({ - top: cursorPos[1] + "px", - left: cursorPos[0] + "px", - display: "block" - }) - - // A BUTTON - if (gp.buttons[0].pressed) { - if (!lastButtonPress["A"]) { - lastButtonPress["A"] = 0 - } - if (Date.now() - lastButtonPress["A"] > buttonPressDelay) { - lastButtonPress["A"] = Date.now() - sounds.Confirm.play() - if (elementType == 0) { - document.activeElement.dispatchEvent(new Event("click")) - document.activeElement.dispatchEvent(new Event("controller-click")) - } else { - element.dispatchEvent(new Event("click")) - element.dispatchEvent(new Event("controller-click")) - } - } - } - - // B BUTTON - if (gp.buttons[1].pressed) { - - if (!lastButtonPress["B"]) { - lastButtonPress["B"] = 0 - } - if (Date.now() - lastButtonPress["B"] > buttonPressDelay) { - lastButtonPress["B"] = Date.now() - if (elementType == 0) { - document.activeElement.dispatchEvent(new Event("contextmenu")) - setTimeout(() => { - if ($(".menu-option").length > 0) { - let bounds = $(".menu-option")[0].getBoundingClientRect() - cursorPos[0] = bounds.left + (bounds.width / 2) - cursorPos[1] = bounds.top + (bounds.height / 2) - } - }, 100) - } else { - element.dispatchEvent(new Event("contextmenu")) - } - } - - } - - // right bumper - if (gp.buttons[5].pressed) { - if (!lastButtonPress["RB"]) { - lastButtonPress["RB"] = 0 - } - if (Date.now() - lastButtonPress["RB"] > buttonPressDelay) { - lastButtonPress["RB"] = Date.now() - app.navigateForward() - - } - } - - // left bumper - if (gp.buttons[4].pressed) { - if (!lastButtonPress["LB"]) { - lastButtonPress["LB"] = 0 - } - if (Date.now() - lastButtonPress["LB"] > buttonPressDelay) { - lastButtonPress["LB"] = Date.now() - app.navigateBack() - - } - } - - - - // cursor hover - if (elementFocusEnabled) { - element = document.elementFromPoint(cursorPos[0], cursorPos[1]) - } - - if (element) { - - let closest = element.closest("[tabindex], input, button, a") - - // VERT SCROLL - let scrollGroupCloY = element.closest(`[scrollaxis="y"]`) - if (scrollGroupCloY) { - scrollGroupY = scrollGroupCloY - } - - - // HOZ SCROLL - let scrollGroupClo = element.closest(".v-hl-container") - - if (scrollGroupClo) { - if (scrollGroupClo.classList.contains("v-hl-container")) { - scrollGroup = scrollGroupClo - scrollGroup.style["scroll-snap-type"] = "unset" - } else { - scrollGroup.style["scroll-snap-type"] = "" - scrollGroup = null - } - } - - if (closest) { - elementType = 0 - closest.focus() - } else { - if (closest) { - closest.blur() - } - elementType = 1 - element.focus() - } - cursorSpeed = cursorSpeedPvt - if (!element.classList.contains("app-chrome") - && !element.classList.contains("app-content")) { - cursorSpeed = cursorSpeedPvt - } - // console.log($._data($(element), "events")) - } else { - cursorSpeed = 12 - } - // console.log(gp.axes[0], gp.axes[1]) - start = requestAnimationFrame(appLoop); - } - - // controller pairing - notyf.error("Press the button on your controller to pair it to Cider.") - window.addEventListener("gamepadconnected", function (e) { - console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", - e.gamepad.index, e.gamepad.id, - e.gamepad.buttons.length, e.gamepad.axes.length); - notyf.success("Pairing successful!") - appLoop() - }, { once: true }); - - document.addEventListener("keydown", (e) => { - sounds.Confirm.currentTime = 0 - sounds.Menu.currentTime = 0 - sounds.Hover.currentTime = 0 - let tabbable = $("[tabindex]") - console.log(e.key) - switch (e.key) { - default: - break; - case "ArrowLeft": - e.preventDefault() - - cursorPos[0] -= cursorSpeed - break; - case "ArrowRight": - e.preventDefault() - - cursorPos[0] += cursorSpeed - break; - case "ArrowUp": - e.preventDefault() - - cursorPos[1] -= cursorSpeed - // sounds.Hover.play() - // if(intTabIndex <= 0) { - // intTabIndex = 0 - // }else{ - // intTabIndex-- - // } - // $(tabbable[intTabIndex]).focus() - // $("#app-content").scrollTop($(document.activeElement).offset().top) - break; - case "ArrowDown": - e.preventDefault() - - cursorPos[1] += cursorSpeed - // if(intTabIndex < tabbable.length) { - // intTabIndex++ - // }else{ - // intTabIndex = tabbable.length - // } - // $(tabbable[intTabIndex]).focus() - // $("#app-content").scrollTop($(document.activeElement).offset().top) - break; - case "c": - app.resetState() - break; - case "x": - // set cursorPos to the top right of the screen - // sounds.Menu.play() - if (elementType == 0) { - document.activeElement.dispatchEvent(new Event("contextmenu")) - } else { - element.dispatchEvent(new Event("contextmenu")) - } - - e.preventDefault() - break; - case "z": - sounds.Confirm.play() - if (elementType == 0) { - document.activeElement.dispatchEvent(new Event("click")) - document.activeElement.dispatchEvent(new Event("controller-click")) - } else { - element.dispatchEvent(new Event("click")) - element.dispatchEvent(new Event("controller-click")) - } - - e.preventDefault() - break; - } - - $(".cursor").css({ - top: cursorPos[1] + "px", - left: cursorPos[0] + "px" - }) - function lerp(a, b, n) { - return (1 - n) * a + n * b - } - - - element = document.elementFromPoint(cursorPos[0], cursorPos[1]) - - if (element) { - let closest = element.closest("[tabindex], input, button, a") - if (closest) { - elementType = 0 - closest.focus() - } else { - elementType = 1 - element.focus() - } - } - console.log(element) - }); - }, - songLinkShare(amUrl) { - notyf.open({ type: "info", className: "notyf-info", message: app.getLz('term.song.link.generate') }) - let self = this - httpRequest = new XMLHttpRequest(); - httpRequest.open('GET', `https://api.song.link/v1-alpha.1/links?url=${amUrl}&userCountry=US`, true); - httpRequest.send(); - httpRequest.onreadystatechange = function () { - if (httpRequest.readyState === 4) { - if (httpRequest.status === 200) { - let response = JSON.parse(httpRequest.responseText); - console.log(response); - self.copyToClipboard(response.pageUrl) - } else { - console.log('There was a problem with the request.'); - notyf.error(app.getLz('term.requestError')) - } - } - } - }, - mainMenuVisibility(val) { - if (val) { - (this.mk.isAuthorized) ? this.chrome.menuOpened = !this.chrome.menuOpened : false; - if (!this.mk.isAuthorized) { - this.mk.authorize() - } - } else { - setTimeout(() => { - this.chrome.menuOpened = false - }, 100) - } - }, - stringTemplateParser(expression, valueObj) { - const templateMatcher = /{{\s?([^{}\s]*)\s?}}/g; - let text = expression.replace(templateMatcher, (substring, value, index) => { - value = valueObj[value]; - return value; - }); - return text - // stringTemplateParser('my name is {{name}} and age is {{age}}', {name: 'Tom', age:100}) - }, - async setLz(lang) { - if (lang == "") { - lang = this.cfg.general.language - } - this.lz = ipcRenderer.sendSync("get-i18n", lang) - this.mklang = await this.MKJSLang() - }, - getLz(message, options = {}) { - if (this.lz[message]) { - if (options["count"]) { - if (typeof this.lz[message] === "object") { - let type = window.fastPluralRules.getPluralFormNameForCardinalByLocale(this.cfg.general.language.replace("_", "-"), options["count"]); - return this.lz[message][type] ?? ((this.lz[message])[Object.keys(this.lz[message])[0]] ?? this.lz[message]) - } else { - // fallback English plural forms ( old i18n ) - if (options["count"] > 1) { - return this.lz[message + "s"] ?? this.lz[message] - } else { - return this.lz[message] ?? this.lz[message + "s"] - } - } - } else if (typeof this.lz[message] === "object") { - return (this.lz[message])[Object.keys(this.lz[message])[0]] - } - return this.lz[message] - } else { - return message - } - }, - setLzManual() { - app.$data.library.songs.sortingOptions = { - "albumName": app.getLz('term.sortBy.album'), - "artistName": app.getLz('term.sortBy.artist'), - "name": app.getLz('term.sortBy.name'), - "genre": app.getLz('term.sortBy.genre'), - "releaseDate": app.getLz('term.sortBy.releaseDate'), - "durationInMillis": app.getLz('term.sortBy.duration'), - "dateAdded": app.getLz('term.sortBy.dateAdded') - } - - app.$data.library.albums.sortingOptions = { - "albumName": app.getLz('term.sortBy.album'), - "artistName": app.getLz('term.sortBy.artist'), - "name": app.getLz('term.sortBy.name'), - "genre": app.getLz('term.sortBy.genre') - } - - app.$data.library.artists.sortingOptions = { - "artistName": app.getLz('term.sortBy.artist'), - "name": app.getLz('term.sortBy.name'), - "genre": app.getLz('term.sortBy.genre'), - "releaseDate": app.getLz('term.sortBy.releaseDate') - } - }, - async showSocialListeningTo() { - let contentIds = Object.keys(app.socialBadges.badgeMap) - app.showCollection({ data: this.socialBadges.mediaItems }, "Friends Listening To", "albums") - if (this.socialBadges.mediaItemDLState == 1 || this.socialBadges.mediaItemDLState == 2) { - return - } - this.socialBadges.mediaItemDLState = 2 - await asyncForEach(contentIds, async (item) => { - try { - let type = "albums" - if (item.includes("pl.")) { - type = "playlists" - } - if (item.includes("ra.")) { - type = "stations" - } - let found = await app.mk.api.v3.music(`/v1/catalog/us/${type}/${item}`) - this.socialBadges.mediaItems.push(found.data.data[0]) - } catch (e) { - - } - }) - }, - async openAppleMusicURL(url) { - let properties = MusicKit.formattedMediaURL(url) - let item = { - id: properties.contentId, - attributes: { - playParams: { - id: properties.contentId, - kind: properties.kind, - } - }, - type: properties.kind, - kind: properties.kind - } - app.routeView(item) - }, - saveFile(fileName, urlFile) { - let a = document.createElement("a"); - a.style = "display: none"; - document.body.appendChild(a); - a.href = urlFile; - a.download = fileName; - a.click(); - window.URL.revokeObjectURL(url); - a.remove(); - }, - async showMenuPanel(data, event) { - app.menuPanel.visible = true; - app.menuPanel.content.name = data.name ?? ""; - app.menuPanel.content.items = data.items ?? {}; - app.menuPanel.content.headerItems = data.headerItems ?? {}; - if (event) { - app.menuPanel.event = event; - } - }, - async getSvgIcon(url) { - let response = await fetch(url); - let data = await response.text(); - return data; - }, - getSocialBadges(cb = () => { - }) { - let self = this - try { - app.mk.api.v3.music("/v1/social/badging-map").then(data => { - self.socialBadges.badgeMap = data.data.results.badgingMap - cb(data.data.results.badgingMap) - }) - } catch (ex) { - this.socialBadges.badgeMap = {} - } - }, - addFavorite(id, type) { - this.cfg.home.favoriteItems.push({ - id: id, - type: type - }); - }, - modularUITest(val = false) { - this.fullscreenLyrics = val; - if (val) { - document.querySelector("#app-main").classList.add("modular-fs") - } else { - document.querySelector("#app-main").classList.remove("modular-fs") - } - }, - navigateBack() { - this.chrome.desiredPageTransition = "wpfade_transform_backwards" - return new Promise((resolve, reject) => { - history.back() - setTimeout(() => { - - - resolve(this.chrome.desiredPageTransition = "wpfade_transform") - }, 100) - }) - }, - navigateForward() { - history.forward() - }, - getHTMLStyle() { - // document.querySelector("html").style.background = "#222"; - document.querySelector("body").classList.add("notransparency") - }, - resetState() { - this.menuPanel.visible = false; - app.selectedMediaItems = []; - this.chrome.contentAreaScrolling = true - for (let key in app.modals) { - app.modals[key] = false; - } - }, - promptAddToPlaylist() { - app.modals.addToPlaylist = true; - }, - async addSelectedToNewPlaylist() { - let self = this - let pl_items = [] - for (let i = 0; i < self.selectedMediaItems.length; i++) { - if (self.selectedMediaItems[i].kind == "song" || self.selectedMediaItems[i].kind == "songs") { - self.selectedMediaItems[i].kind = "songs" - pl_items.push({ - id: self.selectedMediaItems[i].id, - type: self.selectedMediaItems[i].kind - }) - } else if ((self.selectedMediaItems[i].kind == "album" || self.selectedMediaItems[i].kind == "albums") && self.selectedMediaItems[i].isLibrary != true) { - self.selectedMediaItems[i].kind = "albums" - let res = await self.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/albums/${self.selectedMediaItems[i].id}/tracks`); - let ids = res.data.data.map(function (i) { - return { id: i.id, type: i.type } - }) - pl_items = pl_items.concat(ids) - } else if (self.selectedMediaItems[i].kind == "library-song" || self.selectedMediaItems[i].kind == "library-songs") { - self.selectedMediaItems[i].kind = "library-songs" - pl_items.push({ - id: self.selectedMediaItems[i].id, - type: self.selectedMediaItems[i].kind - }) - } 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" - let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`); - let ids = res.data.data.map(function (i) { - return { id: i.id, type: i.type } - }) - pl_items = pl_items.concat(ids) - } else { - pl_items.push({ - id: self.selectedMediaItems[i].id, - type: self.selectedMediaItems[i].kind - }) - } - } - this.modals.addToPlaylist = false - app.newPlaylist(app.getLz('term.newPlaylist'), pl_items) - }, - async addSelectedToPlaylist(playlist_id) { - let self = this - let pl_items = [] - for (let i = 0; i < self.selectedMediaItems.length; i++) { - if (self.selectedMediaItems[i].kind == "song" || self.selectedMediaItems[i].kind == "songs") { - self.selectedMediaItems[i].kind = "songs" - pl_items.push({ - id: self.selectedMediaItems[i].id, - type: self.selectedMediaItems[i].kind - }) - } else if ((self.selectedMediaItems[i].kind == "album" || self.selectedMediaItems[i].kind == "albums") && self.selectedMediaItems[i].isLibrary != true) { - self.selectedMediaItems[i].kind = "albums" - let res = await self.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/albums/${self.selectedMediaItems[i].id}/tracks`); - let ids = res.data.data.map(function (i) { - return { id: i.id, type: i.type } - }) - pl_items = pl_items.concat(ids) - } else if (self.selectedMediaItems[i].kind == "library-song" || self.selectedMediaItems[i].kind == "library-songs") { - self.selectedMediaItems[i].kind = "library-songs" - pl_items.push({ - id: self.selectedMediaItems[i].id, - type: self.selectedMediaItems[i].kind - }) - } 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" - let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`); - let ids = res.data.data.map(function (i) { - return { id: i.id, type: i.type } - }) - pl_items = pl_items.concat(ids) - } else { - pl_items.push({ - id: self.selectedMediaItems[i].id, - type: self.selectedMediaItems[i].kind - }) - } - - } - this.modals.addToPlaylist = false - 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) { - this.getPlaylistFromID(this.showingPlaylist.id, true) - } - }) - }, - async init() { - let self = this - if (this.cfg.visual.theme != "default.less" && this.cfg.visual.theme != "") { - this.setTheme(this.cfg.visual.theme) - } - this.setLz(this.cfg.general.language) - this.setLzManual() - clearTimeout(this.hangtimer) - this.mk = MusicKit.getInstance() - let needsReload = (typeof localStorage["music.ampwebplay.media-user-token"] == "undefined") - this.mk.authorize().then(() => { - self.mkIsReady = true - if (needsReload) { - document.location.reload() - } - }) - this.$forceUpdate() - if (this.isDev) { - this.mk.privateEnabled = true - // Hide UserInfo if Dev mode - } else { - // Get Hide User from Settings - this.chrome.hideUserInfo = !this.cfg.visual.showuserinfo - this.mk.privateEnabled = this.cfg.general.privateEnabled - } - if (this.cfg.visual.hw_acceleration == "disabled") { - document.body.classList.add("no-gpu") - } - this.mk._services.timing.mode = 0 - this.platform = ipcRenderer.sendSync('cider-platform'); - - this.mklang = await this.MKJSLang() - - try { - // Set profile name - this.chrome.userinfo = (await app.mk.api.v3.music(`/v1/me/social-profile`)).data.data[0] - } catch (err) { - } - - this.mk._bag.features['seamless-audio-transitions'] = this.cfg.audio.seamless_audio - - // API Fallback - if (!this.chrome.userinfo) { - this.chrome.userinfo = { - "id": "", - "attributes": { - "name": "Cider User", - "handle": "CiderUser", - "artwork": { "url": "./assets/logocut.png" } - } - } - } - MusicKitInterop.init() - // Set the volume - - // Check the value of this.cfg.audio.muted - if (!this.cfg.audio.muted) { - // Set the mk.volume to the last stored volume data - this.mk.volume = this.cfg.audio.volume - } else if (this.cfg.audio.muted) { - // Set mk.volume to -1 (setting to 0 wont work, so temp solution setting to -1) - this.mk.volume = -1; - } - // ipcRenderer.invoke('getStoreValue', 'audio.volume').then((value) => { - // self.mk.volume = value - // }) - - // load cached library - let librarySongs = await CiderCache.getCache("library-songs") - let libraryAlbums = await CiderCache.getCache("library-albums") - if (librarySongs) { - this.library.songs.listing = librarySongs - this.library.songs.displayListing = this.library.songs.listing - } - if (libraryAlbums) { - this.library.albums.listing = libraryAlbums - this.library.albums.displayListing = this.library.albums.listing - } - - window.onbeforeunload = function (e) { - window.localStorage.setItem("currentTrack", JSON.stringify(app.mk.nowPlayingItem)) - window.localStorage.setItem("currentTime", JSON.stringify(app.mk.currentPlaybackTime)) - window.localStorage.setItem("currentQueue", JSON.stringify(app.mk.queue.items)) - }; - - if (typeof MusicKit.PlaybackBitrate[app.cfg.audio.quality] !== "string") { - app.mk.bitrate = MusicKit.PlaybackBitrate[app.cfg.audio.quality] - } else { - app.mk.bitrate = 256 - app.cfg.audio.quality = "HIGH" - } - - switch (this.cfg.general.resumeOnStartupBehavior) { - default: - case "local": - // load last played track - try { - let lastItem = window.localStorage.getItem("currentTrack") - let time = window.localStorage.getItem("currentTime") - let queue = window.localStorage.getItem("currentQueue") - if (lastItem != null) { - lastItem = JSON.parse(lastItem) - let kind = lastItem.attributes.playParams.kind; - let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; - app.mk.setQueue({ - [truekind]: [lastItem.attributes.playParams.id], - parameters: { l: app.mklang } - }) - app.mk.mute() - setTimeout(() => { - app.mk.play().then(() => { - app.mk.pause().then(() => { - if (time != null) { - app.mk.seekToTime(time) - } - app.mk.unmute() - if (queue != null) { - queue = JSON.parse(queue) - if (queue && queue.length > 0) { - let ids = queue.map(e => (e.playParams ? e.playParams.id : (e.attributes.playParams ? e.attributes.playParams.id : ''))) - let i = 0; - if (ids.length > 0) { - for (id of ids) { - if (!(i == 0 && ids[0] == lastItem.attributes.playParams.id)) { - try { - app.mk.playLater({ songs: [id] }) - } catch (err) { - } - } - i++; - } - } - } - } - - }) - - }) - }, 1500) - - } - - } catch (e) { - console.log(e) - } - break; - case "history": - let history = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, { l: app.mklang }) - if (history.data.data.length > 0) { - let lastItem = history.data.data[0] - let kind = lastItem.attributes.playParams.kind; - let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; - app.mk.setQueue({ - [truekind]: [lastItem.attributes.playParams.id], - parameters: { l: app.mklang } - }) - app.mk.mute() - setTimeout(() => { - app.mk.play().then(() => { - app.mk.pause().then(() => { - app.mk.unmute() - }) - }) - }, 1500) - } - - break; - case "disabled": - - break; - } - - MusicKit.getInstance().videoContainerElement = document.getElementById("apple-music-video-player") - - ipcRenderer.on('theme-update', (event, arg) => { - less.refresh(true, true, true) - self.setTheme(self.cfg.visual.theme, true) - }) - - ipcRenderer.on('SoundCheckTag', (event, tag) => { - // let replaygain = self.parseSCTagToRG(tag) - let soundcheck = tag.split(" ") - let numbers = [] - for (item of soundcheck) { - numbers.push(parseInt(item, 16)) - - } - numbers.shift() - let peak = Math.max(numbers[6], numbers[7]) / 32768.0 - let gain = Math.pow(10, ((-7.63 - (Math.log10(peak) * 20)) / 20))// EBU R 128 Compliant - console.debug(`[Cider][MaikiwiSoundCheck] Peak Gain: ${Math.log10(peak) * 20} | Adjusting '${Math.log10(gain) * 20}' dB`) - try { - //CiderAudio.audioNodes.gainNode.gain.value = (Math.min(Math.pow(10, (replaygain.gain / 20)), (1 / replaygain.peak))) - CiderAudio.audioNodes.gainNode.gain.value = gain - } catch (e) { - } - }) - - ipcRenderer.on('play', function (_event, mode, id) { - if (mode !== 'url') { - self.mk.setQueue({ [mode]: id, parameters: { l: self.mklang } }).then(() => { - app.mk.play() - }) - - } else { - app.openAppleMusicURL(id) - } - }); - - this.mk.addEventListener(MusicKit.Events.playbackStateDidChange, () => { - ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes()); - }) - - this.mk.addEventListener(MusicKit.Events.playbackTimeDidChange, (a) => { - self.lyriccurrenttime = self.mk.currentPlaybackTime - this.currentSongInfo = a - self.playerLCD.playbackDuration = (self.mk.currentPlaybackTime) - // wsapi - ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes()); - }) - - this.mk.addEventListener(MusicKit.Events.nowPlayingItemDidChange, (a) => { - if (self.$refs.queue) { - self.$refs.queue.updateQueue(); - } - this.currentSongInfo = a - - - if (app.cfg.audio.normalization) { - // get unencrypted audio previews to get SoundCheck's normalization tag - try { - let previewURL = null - try { - previewURL = app.mk.nowPlayingItem.previewURL - } catch (e) { - } - if (previewURL == null && ((app.mk.nowPlayingItem?._songId ?? (app.mk.nowPlayingItem["songId"] ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id)) != -1)) { - app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/songs/${app.mk.nowPlayingItem?._songId ?? (app.mk.nowPlayingItem["songId"] ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id)}`).then((response) => { - previewURL = response.data.data[0].attributes.previews[0].url - if (previewURL) - ipcRenderer.send('getPreviewURL', previewURL) - }) - } else { - if (previewURL) - ipcRenderer.send('getPreviewURL', previewURL) - } - - } catch (e) { - } - } - - - try { - a = a.item.attributes; - } catch (_) { - } - let type = (self.mk.nowPlayingItem != null) ? self.mk.nowPlayingItem["type"] ?? '' : ''; - - if (type.includes("musicVideo") || type.includes("uploadedVideo") || type.includes("music-movie")) { - document.getElementById("apple-music-video-container").style.display = "block"; - // app.chrome.topChromeVisible = false - } else { - document.getElementById("apple-music-video-container").style.display = "none"; - // app.chrome.topChromeVisible = true - } - self.chrome.artworkReady = false - self.lyrics = [] - self.richlyrics = [] - app.getCurrentArtURL(); - // app.getNowPlayingArtwork(42); - app.getNowPlayingArtworkBG(32); - app.loadLyrics(); - - // Playback Notifications - if (this.cfg.general.playbackNotifications && !document.hasFocus() && a.artistName && a.artwork && a.name) { - if (this.notification) { - this.notification.close() - } - this.notification = new Notification(a.name, { - body: a.artistName, - icon: a.artwork.url.replace('/{w}x{h}bb', '/512x512bb').replace('/2000x2000bb', '/35x35bb'), - silent: true, - }); - } - - }) - - - this.mk.addEventListener(MusicKit.Events.playbackVolumeDidChange, (_a) => { - this.cfg.audio.volume = this.mk.volume - }) - - this.refreshPlaylists(this.isDev) - document.body.removeAttribute("loading") - if (window.location.hash != "") { - this.appRoute(window.location.hash) - } else { - this.page = "home" - } - - this.mediaKeyFixes() - - setTimeout(() => { - this.getSocialBadges() - this.getBrowsePage(); - this.$forceUpdate() - }, 500) - ipcRenderer.invoke("renderer-ready", true) - document.querySelector("#LOADER").remove() - if (this.cfg.general.themeUpdateNotification) { - this.checkForThemeUpdates() - } - }, - async checkForThemeUpdates() { - let self = this - const themes = ipcRenderer.sendSync("get-themes") - await asyncForEach(themes, async (theme) => { - if (theme.commit != "") { - await fetch(`https://api.github.com/repos/${theme.github_repo}/commits`) - .then(res => res.json()) - .then(res => { - if (res[0].sha != theme.commit) { - const notify = notyf.open({ className: "notyf-info", type: "info", message: `[Themes] ${theme.name} has an update available.` }) - notify.on("click", () => { - app.appRoute("themes-github") - notyf.dismiss(notify) - }) - } - }) - } - }) - }, - async setTheme(theme = "", onlyPrefs = false) { - console.log(theme) - if (this.cfg.visual.theme == "") { - this.cfg.visual.theme = "default.less" - } - if (theme == "") { - theme = this.cfg.visual.theme - } else { - this.cfg.visual.theme = "" - this.cfg.visual.theme = theme - } - const info = {} - try { - const infoResponse = await fetch("themes/" + app.cfg.visual.theme.replace("index.less", "theme.json")) - this.chrome.appliedTheme.info = await infoResponse.json() - } catch (e) { - e = null - console.warn("failed to get theme.json") - this.chrome.appliedTheme.info = {} - } - - - if (!onlyPrefs) { - document.querySelector("#userTheme").href = `themes/${this.cfg.visual.theme}` - document.querySelectorAll(`[id*='less']`).forEach(el => { - el.remove() - }); - less.refresh() - } - }, - getThemeDirective(directive = "") { - let directives = {} - if (typeof this.chrome.appliedTheme.info.directives == "object") { - directives = this.chrome.appliedTheme.info.directives - } - if (directives[directive]) { - return this.chrome.appliedTheme.info.directives[directive].value - } else if (this.cfg.visual.directives[directive]) { - return this.cfg.visual.directives.windowLayout - } else { - return "" - } - }, - unauthorize() { - bootbox.confirm(app.getLz('term.confirmLogout'), function (result) { - if (result) { - app.mk.unauthorize() - document.location.reload() - } - }); - }, - getAppClasses() { - let classes = {} - if (this.cfg.advanced.experiments.includes('compactui')) { - classes.compact = true - } - if (this.cfg.visual.window_background_style == "none") { - classes.simplebg = true - } - - if (this.getThemeDirective('windowLayout') == 'twopanel') { - classes.twopanel = true - } - return classes - }, - invokeDrawer(panel) { - if (this.drawer.panel == panel && this.drawer.open) { - if (panel == "lyrics") { - this.lyricon = false - } - this.drawer.panel = "" - this.drawer.open = false - } else { - if (panel == "lyrics") { - this.lyricon = true - } else { - this.lyricon = false - } - this.drawer.open = true - this.drawer.panel = panel - } - }, - select_removeMediaItem(id) { - this.selectedMediaItems.filter(item => item.guid == id).forEach(item => { - this.selectedMediaItems.splice(this.selectedMediaItems.indexOf(item), 1) - }) - }, - select_hasMediaItem(id) { - let found = this.selectedMediaItems.find(item => item.guid == id) - if (found) { - return true - } else { - return false - } - }, - select_selectMediaItem(id, kind, index, guid, library) { - if (!this.select_hasMediaItem(guid)) { - this.selectedMediaItems.push({ - id: id, - kind: kind, - index: index, - guid: guid, - isLibrary: library - }) - } - }, - getPlaylistFolderChildren(id) { - return this.playlists.listing.filter(playlist => { - if (playlist.parent == id) { - return playlist - } - }) - }, - async refreshPlaylists(localOnly = false, trackMap = true) { - let self = this - let newListing = [] - let trackMapping = {} - const cachedPlaylist = await CiderCache.getCache("library-playlists") - const cachedTrackMapping = await CiderCache.getCache("library-playlists-tracks") - - if (cachedPlaylist) { - console.log("using cached playlists") - this.playlists.listing = cachedPlaylist - self.sortPlaylists() - } else { - console.log("playlist has no cache") - } - - if(cachedTrackMapping) { - console.log("using cached track mapping") - this.playlists.trackMapping = cachedTrackMapping - } - if (localOnly) { - return - } - - this.library.backgroundNotification.message = "Building playlist cache..." - this.library.backgroundNotification.show = true - - async function deepScan(parent = "p.playlistsroot") { - console.log(`scanning ${parent}`) - const playlistData = await app.mk.api.v3.music(`/v1/me/library/playlist-folders/${parent}/children/`) - await asyncForEach(playlistData.data.data, async (playlist) => { - playlist.parent = parent - playlist.children = [] - playlist.tracks = [] - try { - if (trackMap) { - let tracks = await app.mk.api.v3.music(playlist.href + "/tracks").catch(e => { - // no tracks - e = null - }) - tracks.data.data.forEach(track => { - if (!trackMapping[track.id]) { - trackMapping[track.id] = [] - } - trackMapping[track.id].push(playlist.id) - - if (typeof track.attributes.playParams.catalogId == "string") { - if (!trackMapping[track.attributes.playParams.catalogId]) { - trackMapping[track.attributes.playParams.catalogId] = [] - } - trackMapping[track.attributes.playParams.catalogId].push(playlist.id) - } - }) - } - } catch (e) { } - if (playlist.type == "library-playlist-folders") { - try { - await deepScan(playlist.id).catch(e => { }) - } catch (e) { - - } - } - newListing.push(playlist) - }) - } - - await deepScan() - - this.library.backgroundNotification.show = false - this.playlists.listing = newListing - self.sortPlaylists() - if (trackMap) { - CiderCache.putCache("library-playlists-tracks", trackMapping) - this.playlists.trackMapping = trackMapping - } - CiderCache.putCache("library-playlists", newListing) - }, - sortPlaylists() { - this.playlists.listing.sort((a, b) => { - if (a.type === "library-playlist-folders" && b.type !== "library-playlist-folders") { - return -1 - } else if (a.type !== "library-playlist-folders" && b.type === "library-playlist-folders") { - return 1 - } else { - return 0 - } - }) - }, - playlistHeaderContextMenu(event) { - let menu = { - items: [{ - name: app.getLz('term.createNewPlaylist'), - action: () => { - this.newPlaylist() - } - }, - { - name: app.getLz('term.createNewPlaylistFolder'), - action: () => { - this.newPlaylistFolder() - } - } - ] - } - this.showMenuPanel(menu, event) - }, - async editPlaylistFolder(id, name = app.getLz('term.newPlaylist')) { - let self = this - this.mk.api.v3.music( - `/v1/me/library/playlist-folders/${id}`, {}, { - fetchOptions: { - method: "PATCH", - body: JSON.stringify({ - attributes: { name: name } - }) - } - } - ).then(res => { - self.refreshPlaylists() - }) - }, - async editPlaylist(id, name = app.getLz('term.newPlaylist')) { - let self = this - this.mk.api.v3.music( - `/v1/me/library/playlists/${id}`, {}, { - fetchOptions: { - method: "PATCH", - body: JSON.stringify({ - attributes: { name: name } - }) - } - } - ).then(res => { - self.refreshPlaylists() - }) - }, - copyToClipboard(str) { - if (navigator.userAgent.includes('Darwin') || navigator.appVersion.indexOf("Mac") != -1) { - this.darwinShare(str) - } else { - notyf.success(app.getLz('term.share.success')) - navigator.clipboard.writeText(str).then(r => console.log("Copied to clipboard.")) - } - }, - newPlaylist(name = app.getLz('term.newPlaylist'), tracks = []) { - let self = this - let request = { - name: name - } - if (tracks.length > 0) { - request.tracks = tracks - } - 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) - self.appRoute(`playlist_` + res.id); - self.showingPlaylist = []; - self.getPlaylistFromID(app.page.substring(9), true) - self.playlists.listing.push({ - id: res.id, - attributes: { - name: name - }, - parent: "p.playlistsroot" - }) - self.sortPlaylists() - setTimeout(() => { - app.refreshPlaylists() - }, 8000) - }) - }, - deletePlaylist(id) { - let self = this - if (confirm(app.getLz('term.deletePlaylist'))) { - app.mk.api.v3.music(`/v1/me/library/playlists/${id}`, {}, { - fetchOptions: { - method: "DELETE" - } - }).then(res => { - // remove this playlist from playlists.listing if it exists - let found = self.playlists.listing.find(item => item.id == id) - if (found) { - self.playlists.listing.splice(self.playlists.listing.indexOf(found), 1) - } - }) - } - }, - async showCollection(response, title, type, requestBody = {}) { - let self = this - console.log(response) - this.collectionList.requestBody = {} - this.collectionList.response = response - this.collectionList.title = title - this.collectionList.type = type - this.collectionList.requestBody = requestBody - app.appRoute("collection-list") - }, - async showArtistView(artist, title, view) { - let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${artist}/view/${view}?l=${this.mklang}`, {}, { includeResponseMeta: !0 })).data - console.log(response) - await this.showCollection(response, title, "artists") - }, - async showRecordLabelView(label, title, view) { - let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/record-labels/${label}/view/${view}?l=${this.mklang}`)).data - await this.showCollection(response, title, "record-labels") - }, - async showSearchView(term, group, title) { - - let requestBody = { - platform: "web", - groups: group, - types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels", - limit: 25, - relate: { - editorialItems: ["contents"] - }, - include: { - albums: ["artists"], - songs: ["artists"], - "music-videos": ["artists"] - }, - extend: "artistUrl", - fields: { - artists: "url,name,artwork,hero", - albums: "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url" - }, - with: "serverBubbles,lyricHighlights", - art: { - "url": "cf" - }, - omit: { - resource: ["autos"] - }, - groups: group, - l: this.mklang - } - 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) { - response = response.data.data[0] - let self = this - let playlistId = response.id - this.playlists.loadingState = (!transient) ? 0 : 1 - this.showingPlaylist = response - if (!response.relationships?.tracks?.next) { - this.playlists.loadingState = 1 - return - } - - function getPlaylistTracks(next) { - app.apiCall(app.musicBaseUrl + next, res => { - if (self.showingPlaylist.id != playlistId) { - return - } - self.showingPlaylist.relationships.tracks.data = self.showingPlaylist.relationships.tracks.data.concat(res.data) - if (res.next) { - getPlaylistTracks(res.next) - } else { - self.playlists.loadingState = 1 - } - }) - } - - getPlaylistTracks(response.relationships.tracks.next) - - }, - async getPlaylistFromID(id, transient = false) { - let self = this - const params = { - include: "tracks", - platform: "web", - "include[library-playlists]": "catalog,tracks", - "fields[playlists]": "curatorName,playlistType,name,artwork,url,playParams", - "include[library-songs]": "catalog,artists,albums,playParams,name,artwork,url", - "fields[catalog]": "artistUrl,albumUrl,url", - "fields[songs]": "artistUrl,albumUrl,playParams,name,artwork,url,artistName,albumName,durationInMillis", - l: this.mklang - } - if (!transient) { - this.playlists.loadingState = 0; - } - app.mk.api.v3.music(`/v1/me/library/playlists/${id}`, params).then(res => { - self.getPlaylistContinuous(res, transient) - }).catch((e) => { - console.log(e); - try { - app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${id}`, params).then(res => { - self.getPlaylistContinuous(res, transient) - }) - } catch (err) { - console.log(err) - } - }) - - }, - async getArtistFromID(id) { - this.page = "" - const artistData = await this.mkapi("artists", false, id, { - "views": "featured-release,full-albums,appears-on-albums,featured-albums,featured-on-albums,singles,compilation-albums,live-albums,latest-release,top-music-videos,similar-artists,top-songs,playlists,more-to-hear,more-to-see", - "extend": "artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero", - "extend[playlists]": "trackCount", - "include[songs]": "albums", - "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount", - "limit[artists:top-songs]": 20, - "art[url]": "f", - l: this.mklang - }, { includeResponseMeta: !0 }) - console.log(artistData.data.data[0]) - this.artistPage.data = artistData.data.data[0] - this.page = "artist-page" - }, - progressBarStyle() { - let val = this.playerLCD.playbackDuration - if (this.playerLCD.desiredDuration > 0) { - val = this.playerLCD.desiredDuration - } - let min = 0 - let max = this.mk.currentPlaybackDuration - let value = (val - min) / (max - min) * 100 - return { - 'background': ('linear-gradient(to right, var(--songProgressColor) 0%, var(--songProgressColor) ' + value + '%, var(--songProgressBackground) ' + value + '%, var(--songProgressBackground) 100%)') - } - }, - async getRecursive(response) { - // if response has a .next() property run it and keep running until .next is null or undefined - // and then return the response concatenated with the results of the next() call - function executeRequest() { - if (response.next) { - return response.next().then(executeRequest) - } else { - return response - } - } - - return executeRequest() - }, - async getRecursive2(response, sendTo) { - let returnData = { - "data": [], - "meta": {} - } - if (response.next) { - console.log("has next") - returnData.data.concat(response.data) - returnData.meta = response.meta - return await this.getRecursive(await response.next()) - } else { - console.log("no next") - returnData.data.concat(response.data) - return returnData - } - }, - async getSearchHints() { - if (this.search.term == "") { - this.search.hints = [] - return - } - 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 : [] - }, - getSongProgress() { - if (this.playerLCD.userInteraction) { - return this.playerLCD.desiredDuration - } else { - return this.playerLCD.playbackDuration - } - }, - /** - * Converts seconds to dd:hh:mm:ss - * @param time (in seconds) - * @param format (short, long) - * @returns {string} - */ - convertTime(time, format = 'short') { - if (typeof time !== "number") { - time = parseInt(time) - } - - const timeGates = { - 600: 15, // 10 Minutes - 3600: 14, // Hour - 36000: 12, // 10 Hours - } - - const datetime = new Date(time * 1000) - - let returnTime = datetime.toISOString().substring(11, 19); - for (let key in timeGates) { - if (time < key) { - returnTime = datetime.toISOString().substring(timeGates[key], 19) - break - } - } - - // Add the days on the front - let day; - if (time >= 86400) { - day = datetime.toISOString().substring(8, 10) - day = parseInt(day) - 1 - returnTime = day + ":" + returnTime - } - - if (format === 'long') { - const longFormat = [] - - // Seconds - if (datetime.getSeconds() !== 0) { - longFormat.push(`${datetime.getSeconds()} ${app.getLz('term.time.seconds')}`) - } - - // Minutes - if (time >= 60) { - longFormat.push(`${datetime.getMinutes()} ${app.getLz('term.time.minute', options = { count: datetime.getMinutes() })}`) - } - - // Hours - if (time >= 3600) { - longFormat.push(`${datetime.getHours()} ${app.getLz('term.time.hour', options = { count: datetime.getHours() })}`) - } - - // Days - if (time >= 86400) { - longFormat.push(`${day} ${app.getLz('term.time.day', options = { count: day })}`) - } - returnTime = longFormat.reverse().join(', ') - } - - return returnTime - }, - hashCode(str) { - let hash = 0, - i, chr; - if (str.length === 0) return hash; - for (i = 0; i < str.length; i++) { - chr = str.charCodeAt(i); - hash = ((hash << 5) - hash) + chr; - hash |= 0; // Convert to 32bit integer - } - return hash; - }, - appRoute(route) { - console.log(route) - if (route == "" || route == "#" || route == "/") { - return; - } - route = route.replace(/#/g, "") - // if the route contains does not include a / then route to the page directly - if (route.indexOf("/") == -1) { - this.page = route - window.location.hash = this.page - // if (this.page == "settings") { - // this.version - // } - return - } - let hash = route.split("/") - let page = hash[0] - let id = hash[1] - let isLibrary = hash[2] ?? false - console.log(`page: ${page} id: ${id} isLibrary: ${isLibrary}`) - this.routeView({ - kind: page, - id: id, - attributes: { - playParams: { kind: page, id: id, isLibrary: isLibrary } - } - }) - }, - routeView(item) { - let kind = (item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')) : (item.type ?? '')); - let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? '')); - ; - let isLibrary = item.attributes.playParams ? (item.attributes.playParams.isLibrary ?? false) : false; - console.log(kind, id, isLibrary) - if (kind.includes("playlist") || kind.includes("album")) { - app.showingPlaylist = []; - } - if (kind.toString().includes("apple-curator")) { - kind = "appleCurator" - app.getTypeFromID("appleCurator", (id), false, { - platform: "web", - include: "grouping,playlists", - extend: "editorialArtwork", - "art[url]": "f" - }); - window.location.hash = `${kind}/${id}` - document.querySelector("#app-content").scrollTop = 0 - } else if (kind.toString().includes("artist")) { - app.getArtistInfo(id, isLibrary) - window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ''}` - document.querySelector("#app-content").scrollTop = 0 - - } else if (kind.toString().includes("record-label") || kind.toString().includes("curator")) { - if (kind.toString().includes("record-label")) { - kind = "recordLabel" - } else { - kind = "curator" - } - app.page = (kind) + "_" + (id); - app.getTypeFromID((kind), (id), (isLibrary), { - extend: "editorialVideo", - include: 'grouping,playlists', - views: 'top-releases,latest-releases,top-artists' - }); - window.location.hash = `${kind}/${id}` - document.querySelector("#app-content").scrollTop = 0 - } else if (!kind.toString().includes("radioStation") && !kind.toString().includes("song") && !kind.toString().includes("musicVideo") && !kind.toString().includes("uploadedVideo") && !kind.toString().includes("music-movie")) { - let params = { - extend: "offers,editorialVideo", - "views": "appears-on,more-by-artist,related-videos,other-versions,you-might-also-like,video-extras,audio-extras", - } - if (kind.includes("playlist")) { - params["include"] = "tracks"; - } - if (kind.includes("album")) { - params["include[albums]"] = "artists" - params["fields[artists]"] = "name,url" - params["fields[albums]"] = "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,copyright" - } - - if (this.cfg.advanced.experiments.includes('inline-playlists')) { - let showModal = kind.toString().includes("album") || kind.toString().includes("playlist") - if (app.page.includes("playlist") || app.page.includes("album")) { - showModal = false - } - if (showModal) { - app.modals.showPlaylist = true - app.chrome.contentAreaScrolling = false - } else { - app.page = (kind) + "_" + (id); - window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ''}` - } - } else { - app.page = (kind) + "_" + (id); - window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ''}` - } - - - app.getTypeFromID((kind), (id), (isLibrary), params); - // document.querySelector("#app-content").scrollTop = 0 - } else { - app.playMediaItemById((id), (kind), (isLibrary), item.attributes.url ?? '') - } - }, - prevButton() { - if (!app.prevButtonBackIndicator && app.mk.nowPlayingItem && app.mk.currentPlaybackTime > 2) { - app.prevButtonBackIndicator = true; - try { - clearTimeout(app.pauseButtonTimer) - } catch (e) { - } - app.mk.seekToTime(0); - app.pauseButtonTimer = setTimeout(() => { - app.prevButtonBackIndicator = false - }, 3000); - } else { - try { - clearTimeout(app.pauseButtonTimer) - } catch (e) { - } - app.prevButtonBackIndicator = false; - app.skipToPreviousItem() - } - }, - async getNowPlayingItemDetailed(target) { - try { - 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", l: app.mklang }); - app.searchAndNavigate(u.data.data[0], target) - } catch (e) { - app.searchAndNavigate(app.mk.nowPlayingItem, target) - } - }, - async searchAndNavigate(item, target) { - let self = this - app.tmpVar = item; - switch (target) { - case "artist": - let artistId = ''; - try { - if (item.relationships.artists && item.relationships.artists.data.length > 0 && !item.relationships.artists.data[0].type.includes("library")) { - if (item.relationships.artists.data[0].type === "artist" || item.relationships.artists.data[0].type === "artists") { - artistId = item.relationships.artists.data[0].id - } - } - if (artistId == '') { - const url = (item.relationships.catalog.data[0].attributes.artistUrl); - artistId = (url).substring(url.lastIndexOf('/') + 1) - if (artistId.includes('viewCollaboration')) { - artistId = artistId.substring(artistId.lastIndexOf('ids=') + 4, artistId.lastIndexOf('-')) - } - } - } catch (_) { - } - - if (artistId == "") { - let artistQuery = (await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${item.attributes.artistName}`, { - limit: 1, - types: 'artists' - })).data.results; - try { - if (artistQuery.artists.data.length > 0) { - artistId = artistQuery.artists.data[0].id; - console.log(artistId) - } - } catch (e) { - } - } - console.log(artistId); - if (artistId != "") - self.appRoute(`artist/${artistId}`) - break; - case "album": - let albumId = ''; - try { - if ((item.type ?? item.playParams?.kind ?? "") == "albums") { - albumId = item.id ?? "" - } else if (item.relationships.albums && item.relationships.albums.data.length > 0 && !item.relationships.albums.data[0].type.includes("library")) { - if (item.relationships.albums.data[0].type === "album" || item.relationships.albums.data[0].type === "albums") { - albumId = item.relationships.albums.data[0].id - } - } - if (albumId == '') { - const url = (item.relationships.catalog.data[0].attributes.url); - albumId = (url).substring(url.lastIndexOf('/') + 1) - if (albumId.includes("?i=")) { - albumId = albumId.substring(0, albumId.indexOf("?i=")) - } - } - } catch (_) { - } - - if (albumId == "") { - try { - let albumQuery = (await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${(item.attributes.albumName ?? item.attributes.name ?? "") + " " + (item.attributes.artistName ?? "")}`, { - limit: 1, - types: 'albums' - })).data.results; - if (albumQuery.albums.data.length > 0) { - albumId = albumQuery.albums.data[0].id; - console.log(albumId) - } - } catch (e) { - } - } - if (albumId != "") { - self.appRoute(`album/${albumId}`) - } - break; - case "recordLabel": - let labelId = ''; - try { - labelId = item.relationships['record-labels'].data[0].id - } catch (_) { - } - - if (labelId == "") { - try { - let labelQuery = (await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${item.attributes.recordLabel}`, { - limit: 1, - types: 'record-labels' - })).data.results; - if (labelQuery["record-labels"].data.length > 0) { - labelId = labelQuery["record-labels"].data[0].id; - console.log(labelId) - } - } catch (e) { - } - } - if (labelId != "") { - app.showingPlaylist = [] - await app.getTypeFromID("recordLabel", labelId, false, { views: 'top-releases,latest-releases,top-artists' }); - app.page = "recordLabel_" + labelId; - } - - break; - } - }, - exitMV() { - MusicKit.getInstance().stop() - document.getElementById("apple-music-video-container").style.display = "none"; - }, - getArtistInfo(id, isLibrary) { - this.getArtistFromID(id) - //this.getTypeFromID("artist",id,isLibrary,query) - }, - followArtistById(id, follow) { - if (follow && !this.followingArtist(id)) { - this.cfg.home.followedArtists.push(id) - } else { - let index = this.cfg.home.followedArtists.indexOf(id) - if (index > -1) { - this.cfg.home.followedArtists.splice(index, 1) - } - } - }, - followingArtist(id) { - console.log(`check for ${id}`) - return this.cfg.home.followedArtists.includes(id) - }, - playMediaItem(item) { - let kind = (item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')) : (item.type ?? '')); - let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? '')); - ; - let isLibrary = item.attributes.playParams ? (item.attributes.playParams.isLibrary ?? false) : false; - let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; - console.log(kind, id, isLibrary) - app.mk.stop().then(() => { - if (kind.includes("artist")) { - app.mk.setStationQueue({ artist: 'a-' + id }).then(() => { - app.mk.play() - }) - } - // else if (kind.includes("playlist") && (id.startsWith("p.") || id.startsWith("pl."))){ - // /* Randomize array in-place using Durstenfeld shuffle algorithm */ - // function shuffleArray(array) { - // for (var i = array.length - 1; i > 0; i--) { - // var j = Math.floor(Math.random() * (i + 1)); - // var temp = array[i]; - // array[i] = array[j]; - // array[j] = temp; - // } - // } - // app.mk.clearQueue().then(function () { { - // app.mk.setQueue({[truekind]: [item.attributes.playParams.id ?? item.id]}).then(function () { - // app.mk.play().then(function (){ - // app.mk.clearQueue().then(function (){ - // var playlistId = id - // const params = { - // include: "tracks", - // platform: "web", - // "include[library-playlists]": "catalog,tracks", - // "fields[playlists]": "curatorName,playlistType,name,artwork,url", - // "include[library-songs]": "catalog,artists,albums", - // "fields[catalog]": "artistUrl,albumUrl", - // "fields[songs]": "artistUrl,albumUrl" - // } - // var playlistId = '' - - // try { - // function getPlaylist(id, params, isLibrary){ - // if (isLibrary){ - // return app.mk.api.library.playlist(id, params) - // } else { return app.mk.api.playlist(id, params)} - // } - // getPlaylist(id, params, isLibrary).then(res => { - // let query = res.relationships.tracks.data.map(item => new MusicKit.MediaItem(item)); - // if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')} - // app.mk.queue.append(query) - // if (!res.relationships.tracks.next) { - // return - // } else { - // getPlaylistTracks(res.relationships.tracks.next) - // } - - // function getPlaylistTracks(next) { - // app.apiCall(app.musicBaseUrl + next, res => { - // if (res.id != playlistId) { - // return - // } - // let query = res.data.map(item => new MusicKit.MediaItem(item)) - // if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')} - // app.mk.queue.append(query) - - // if (res.next) { - // getPlaylistTracks(res.next) - // } - // }) - // } - // }) - // } catch (e) {} - - - // }) - // }) - // }) - // } - // }) - // } - else { - app.playMediaItemById((id), (kind), (isLibrary), item.attributes.url ?? '') - } - }) - }, - async getTypeFromID(kind, id, isLibrary = false, params = {}, params2 = {}) { - let a; - if (kind == "album" | kind == "albums") { - params["include"] = "tracks,artists,record-labels,catalog"; - } - params['l'] = this.mklang; - try { - a = await this.mkapi(kind.toString(), isLibrary, id.toString(), params, params2); - } catch (e) { - console.log(e); - try { - a = await this.mkapi(kind.toString(), !isLibrary, id.toString(), params, params2); - } catch (err) { - console.log(err); - a = [] - } finally { - if (kind == "appleCurator") { - app.appleCurator = a.data.data[0] - } else { - this.getPlaylistContinuous(a, true) - } - } - } finally { - if (kind == "appleCurator") { - app.appleCurator = a.data.data[0] - } else { - this.getPlaylistContinuous(a, true) - } - } - ; - }, - searchLibrarySongs() { - let self = this - let prefs = this.cfg.libraryPrefs.songs - let albumAdded = self.library?.albums?.listing?.map(function (i) { - return { [i.id]: i.attributes?.dateAdded } - }) - let startTime = new Date().getTime() - - function sortSongs() { - // sort this.library.songs.displayListing by song.attributes[self.library.songs.sorting] in descending or ascending order based on alphabetical order and numeric order - // check if song.attributes[self.library.songs.sorting] is a number and if so, sort by number if not, sort by alphabetical order ignoring case - self.library.songs.displayListing.sort((a, b) => { - let aa = a.attributes[prefs.sort] - let bb = b.attributes[prefs.sort] - if (prefs.sort == "genre") { - aa = a.attributes.genreNames[0] - bb = b.attributes.genreNames[0] - } - if (prefs.sort == "dateAdded") { - let albumida = a.relationships?.albums?.data[0]?.id ?? '1970-01-01T00:01:01Z' - let albumidb = b.relationships?.albums?.data[0]?.id ?? '1970-01-01T00:01:01Z' - aa = startTime - new Date(((albumAdded.find(i => i[albumida])) ?? [])[albumida] ?? '1970-01-01T00:01:01Z').getTime() - bb = startTime - new Date(((albumAdded.find(i => i[albumidb])) ?? [])[albumidb] ?? '1970-01-01T00:01:01Z').getTime() - } - if (aa == null) { - aa = "" - } - if (bb == null) { - bb = "" - } - if (prefs.sortOrder == "asc") { - if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { - return aa - bb - } else { - return aa.toString().toLowerCase().localeCompare(bb.toString().toLowerCase()) - } - } else if (prefs.sortOrder == "desc") { - if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { - return bb - aa - } else { - return bb.toString().toLowerCase().localeCompare(aa.toString().toLowerCase()) - } - } - }) - } - - if (this.library.songs.search == "") { - this.library.songs.displayListing = this.library.songs.listing - sortSongs() - } else { - this.library.songs.displayListing = this.library.songs.listing.filter(item => { - let itemName = item.attributes.name.toLowerCase() - let searchTerm = this.library.songs.search.toLowerCase() - let artistName = "" - let albumName = "" - if (item.attributes.artistName != null) { - artistName = item.attributes.artistName.toLowerCase() - } - if (item.attributes.albumName != null) { - albumName = item.attributes.albumName.toLowerCase() - } - - // remove any non-alphanumeric characters and spaces from search term and item name - searchTerm = searchTerm.replace(/[^a-z0-9 ]/gi, "") - itemName = itemName.replace(/[^a-z0-9 ]/gi, "") - artistName = artistName.replace(/[^a-z0-9 ]/gi, "") - albumName = albumName.replace(/[^a-z0-9 ]/gi, "") - - if (itemName.includes(searchTerm) || artistName.includes(searchTerm) || albumName.includes(searchTerm)) { - return item - } - }) - sortSongs() - } - }, - getAlbumSort() { - this.library.albums.sortOrder[1] = this.cfg.libraryPrefs.albums.sortOrder; - this.library.albums.sorting[1] = this.cfg.libraryPrefs.albums.sort; - }, - // make a copy of searchLibrarySongs except use Albums instead of Songs - searchLibraryAlbums(index) { - let self = this - - function sortAlbums() { - // sort this.library.albums.displayListing by album.attributes[self.library.albums.sorting[index]] in descending or ascending order based on alphabetical order and numeric order - // check if album.attributes[self.library.albums.sorting[index]] is a number and if so, sort by number if not, sort by alphabetical order ignoring case - self.library.albums.displayListing.sort((a, b) => { - let aa = a.attributes[self.library.albums.sorting[index]] - let bb = b.attributes[self.library.albums.sorting[index]] - if (self.library.albums.sorting[index] == "genre") { - aa = a.attributes.genreNames[0] - bb = b.attributes.genreNames[0] - } - if (aa == null) { - aa = "" - } - if (bb == null) { - bb = "" - } - if (self.library.albums.sortOrder[index] == "asc") { - if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { - return aa - bb - } else { - return aa.toString().toLowerCase().localeCompare(bb.toString().toLowerCase()) - } - } else if (self.library.albums.sortOrder[index] == "desc") { - if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { - return bb - aa - } else { - return bb.toString().toLowerCase().localeCompare(aa.toString().toLowerCase()) - } - } - }) - } - - if (this.library.albums.search == "") { - this.library.albums.displayListing = this.library.albums.listing - sortAlbums() - } else { - this.library.albums.displayListing = this.library.albums.listing.filter(item => { - let itemName = item.attributes.name.toLowerCase() - let searchTerm = this.library.albums.search.toLowerCase() - let artistName = "" - let albumName = "" - if (item.attributes.artistName != null) { - artistName = item.attributes.artistName.toLowerCase() - } - if (item.attributes.albumName != null) { - albumName = item.attributes.albumName.toLowerCase() - } - - // remove any non-alphanumeric characters and spaces from search term and item name - searchTerm = searchTerm.replace(/[^a-z0-9 ]/gi, "") - itemName = itemName.replace(/[^a-z0-9 ]/gi, "") - artistName = artistName.replace(/[^a-z0-9 ]/gi, "") - albumName = albumName.replace(/[^a-z0-9 ]/gi, "") - - if (itemName.includes(searchTerm) || artistName.includes(searchTerm) || albumName.includes(searchTerm)) { - return item - } - }) - sortAlbums() - } - }, - // make a copy of searchLibrarySongs except use Albums instead of Songs - searchLibraryArtists(index) { - let self = this - - function sortArtists() { - // sort this.library.albums.displayListing by album.attributes[self.library.albums.sorting[index]] in descending or ascending order based on alphabetical order and numeric order - // check if album.attributes[self.library.albums.sorting[index]] is a number and if so, sort by number if not, sort by alphabetical order ignoring case - self.library.artists.displayListing.sort((a, b) => { - let aa = a.attributes[self.library.artists.sorting[index]] - let bb = b.attributes[self.library.artists.sorting[index]] - if (self.library.artists.sorting[index] == "genre") { - aa = a.attributes.genreNames[0] - bb = b.attributes.genreNames[0] - } - if (aa == null) { - aa = "" - } - if (bb == null) { - bb = "" - } - if (self.library.artists.sortOrder[index] == "asc") { - if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { - return aa - bb - } else { - return aa.toString().toLowerCase().localeCompare(bb.toString().toLowerCase()) - } - } else if (self.library.artists.sortOrder[index] == "desc") { - if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { - return bb - aa - } else { - return bb.toString().toLowerCase().localeCompare(aa.toString().toLowerCase()) - } - } - }) - } - - if (this.library.artists.search == "") { - this.library.artists.displayListing = this.library.artists.listing - sortArtists() - } else { - this.library.artists.displayListing = this.library.artists.listing.filter(item => { - let itemName = item.attributes.name.toLowerCase() - let searchTerm = this.library.artists.search.toLowerCase() - let artistName = "" - let albumName = "" - // if (item.attributes.artistName != null) { - // artistName = item.attributes.artistName.toLowerCase() - // } - // if (item.attributes.albumName != null) { - // albumName = item.attributes.albumName.toLowerCase() - // } - - // remove any non-alphanumeric characters and spaces from search term and item name - searchTerm = searchTerm.replace(/[^a-z0-9 ]/gi, "") - itemName = itemName.replace(/[^a-z0-9 ]/gi, "") - - - if (itemName.includes(searchTerm) || artistName.includes(searchTerm) || albumName.includes(searchTerm)) { - return item - } - }) - sortArtists() - } - }, - getSidebarItemClass(page) { - if (this.page == page) { - return ["active"] - } else { - return [] - } - }, - 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) { - return - } - let truemethod = (!method.endsWith("s")) ? (method + "s") : method; - try { - if (library) { - return await this.mk.api.v3.music(`v1/me/library/${truemethod}/${term.toString()}`, params, params2) - } else { - return await this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/${truemethod}/${term.toString()}`, params, params2) - } - } catch (e) { - console.log(e) - return await this.mkapi(method, library, term, params, params2, attempts + 1) - } - }, - getLibraryGenres() { - let genres = [] - genres = [] - this.library.songs.listing.forEach((item) => { - item.attributes.genreNames.forEach((genre) => { - if (!genres.includes(genre)) { - genres.push(genre) - } - }) - }) - return genres - }, - async getLibrarySongsFull(force = false) { - let self = this - let library = [] - let cacheId = "library-songs" - let downloaded = null; - if ((this.library.songs.downloadState == 2) && !force) { - return - } - if (this.library.songs.downloadState == 1) { - return - } - let librarySongs = await CiderCache.getCache(cacheId) - if (librarySongs) { - this.library.songs.listing = librarySongs - this.searchLibrarySongs() - } - if (this.songstest) { - return - } - this.library.songs.downloadState = 1 - this.library.backgroundNotification.show = true - this.library.backgroundNotification.message = app.getLz('notification.updatingLibrarySongs') - - function downloadChunk() { - const params = { - "include[library-songs]": "catalog,artists,albums", - "fields[artists]": "name,url,id", - "fields[albums]": "name,url,id", - platform: "web", - "fields[catalog]": "artistUrl,albumUrl", - "fields[songs]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", - limit: 100, - l: self.mklang - } - const safeparams = { - "platform": "web", - "limit": 80 - } - self.library.songs.downloadState = 1 - if (downloaded == null) { - app.mk.api.v3.music(`/v1/me/library/songs/`, params).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading'); - app.mk.api.v3.music(`/v1/me/library/songs/`, safeparams).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading failed', error) - app.library.songs.downloadState = 2 - app.library.backgroundNotification.show = false - }) - }) - } else { - if (downloaded.next != null) { - app.mk.api.v3.music(downloaded.next, params).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading'); - app.mk.api.v3.music(downloaded.next, safeparams).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading failed', error) - app.library.songs.downloadState = 2 - app.library.backgroundNotification.show = false - }) - }) - } else { - console.log("Download next", downloaded.next) - } - } - } - - function processChunk(response) { - downloaded = response - library = library.concat(downloaded.data) - self.library.backgroundNotification.show = true - self.library.backgroundNotification.message = app.getLz('notification.updatingLibrarySongs') - self.library.backgroundNotification.total = downloaded.meta.total - self.library.backgroundNotification.progress = library.length - - if (downloaded.meta.total == 0) { - self.library.songs.downloadState = 3 - return - } - if (typeof downloaded.next == "undefined") { - console.log("downloaded.next is undefined") - self.library.songs.listing = library - self.library.songs.downloadState = 2 - self.library.backgroundNotification.show = false - self.searchLibrarySongs() - CiderCache.putCache(cacheId, library) - } - if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { - console.log(`downloading next chunk - ${library.length} songs so far`) - downloadChunk() - } else { - self.library.songs.listing = library - self.library.songs.downloadState = 2 - self.library.backgroundNotification.show = false - self.searchLibrarySongs() - CiderCache.putCache(cacheId, library) - // console.log(library) - } - } - - downloadChunk() - }, - // copy the getLibrarySongsFull function except change Songs to Albums - async getLibraryAlbumsFull(force = false, index) { - let self = this - let library = [] - let cacheId = "library-albums" - let downloaded = null; - if ((this.library.albums.downloadState == 2 || this.library.albums.downloadState == 1) && !force) { - return - } - let libraryAlbums = await CiderCache.getCache(cacheId) - if (libraryAlbums) { - this.library.albums.listing = libraryAlbums - this.searchLibraryAlbums(index) - } - if (this.songstest) { - return - } - this.library.albums.downloadState = 1 - this.library.backgroundNotification.show = true - this.library.backgroundNotification.message = app.getLz('notification.updatingLibraryAlbums') - - function downloadChunk() { - self.library.albums.downloadState = 1 - const params = { - "include[library-albums]": "catalog,artists,albums", - "fields[artists]": "name,url,id", - "fields[albums]": "name,url,id", - platform: "web", - "fields[catalog]": "artistUrl,albumUrl", - "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", - limit: 100, - l: self.mklang - } - const safeparams = { - platform: "web", - limit: "60", - "include[library-albums]": "artists", - "include[library-artists]": "catalog", - "include[albums]": "artists", - "fields[artists]": "name,url", - "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", - "includeOnly": "catalog,artists" - } - if (downloaded == null) { - app.mk.api.v3.music(`/v1/me/library/albums/`, params).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading'); - app.mk.api.v3.music(`/v1/me/library/albums/`, safeparams).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading failed', error) - app.library.albums.downloadState = 2 - app.library.backgroundNotification.show = false - }) - }) - } else { - if (downloaded.next != null) { - app.mk.api.v3.music(downloaded.next, params).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading'); - app.mk.api.v3.music(downloaded.next, safeparams).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading failed', error); - app.library.albums.downloadState = 2 - app.library.backgroundNotification.show = false - }) - }) - } else { - console.log("Download next", downloaded.next) - } - } - } - - function processChunk(response) { - downloaded = response - library = library.concat(downloaded.data) - self.library.backgroundNotification.show = true - self.library.backgroundNotification.message = app.getLz('notification.updatingLibraryAlbums') - self.library.backgroundNotification.total = downloaded.meta.total - self.library.backgroundNotification.progress = library.length - if (downloaded.meta.total == 0) { - self.library.albums.downloadState = 3 - return - } - if (typeof downloaded.next == "undefined") { - console.log("downloaded.next is undefined") - self.library.albums.listing = library - self.library.albums.downloadState = 2 - self.library.backgroundNotification.show = false - CiderCache.putCache(cacheId, library) - self.searchLibraryAlbums(index) - } - if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { - console.log(`downloading next chunk - ${library.length - } albums so far`) - downloadChunk() - } else { - self.library.albums.listing = library - self.library.albums.downloadState = 2 - self.library.backgroundNotification.show = false - CiderCache.putCache(cacheId, library) - self.searchLibraryAlbums(index) - // console.log(library) - } - } - - downloadChunk() - }, - // copy the getLibrarySongsFull function except change Songs to Albums - async getLibraryArtistsFull(force = false, index) { - let self = this - let library = [] - let cacheId = "library-artists" - let downloaded = null; - if ((this.library.artists.downloadState == 2 || this.library.artists.downloadState == 1) && !force) { - return - } - let libraryArtists = await CiderCache.getCache(cacheId) - if (libraryArtists) { - this.library.artists.listing = libraryArtists - this.searchLibraryArtists(index) - } - if (this.songstest) { - return - } - this.library.artists.downloadState = 1 - this.library.backgroundNotification.show = true - this.library.backgroundNotification.message = app.getLz('notification.updatingLibraryArtists') - - function downloadChunk() { - self.library.artists.downloadState = 1 - const params = { - include: "catalog", - // "include[library-artists]": "catalog,artists,albums", - // "fields[artists]": "name,url,id", - // "fields[albums]": "name,url,id", - platform: "web", - // "fields[catalog]": "artistUrl,albumUrl", - // "fields[artists]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", - limit: 100, - l: self.mklang - } - const safeparams = { - include: "catalog", - platform: "web", - limit: 50, - } - if (downloaded == null) { - app.mk.api.v3.music(`/v1/me/library/artists/`, params).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading'); - app.mk.api.v3.music(`/v1/me/library/artists/`, safeparams).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading failed', error) - app.library.artists.downloadState = 2 - app.library.backgroundNotification.show = false - }) - }) - - } else { - if (downloaded.next != null) { - app.mk.api.v3.music(downloaded.next, params).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading'); - app.mk.api.v3.music(downloaded.next, safeparams).then((response) => { - processChunk(response.data) - }).catch((error) => { - console.log('safe loading failed', error) - app.library.artists.downloadState = 2 - app.library.backgroundNotification.show = false - }) - }) - } else { - console.log("Download next", downloaded.next) - } - - } - } - - function processChunk(response) { - downloaded = response - library = library.concat(downloaded.data) - self.library.backgroundNotification.show = true - self.library.backgroundNotification.message = app.getLz('notification.updatingLibraryArtists') - self.library.backgroundNotification.total = downloaded.meta.total - self.library.backgroundNotification.progress = library.length - if (downloaded.meta.total == 0) { - self.library.albums.downloadState = 3 - return - } - if (typeof downloaded.next == "undefined") { - console.log("downloaded.next is undefined") - self.library.artists.listing = library - self.library.artists.downloadState = 2 - self.library.artists.show = false - CiderCache.putCache(cacheId, library) - self.searchLibraryArtists(index) - } - if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { - console.log(`downloading next chunk - ${library.length - } artists so far`) - downloadChunk() - } else { - self.library.artists.listing = library - self.library.artists.downloadState = 2 - self.library.backgroundNotification.show = false - CiderCache.putCache(cacheId, library) - self.searchLibraryArtists(index) - // console.log(library) - } - } - - downloadChunk() - }, - getTotalTime() { - try { - if (app.showingPlaylist.relationships.tracks.data.length > 0) { - const timeInSeconds = Math.round([].concat(...app.showingPlaylist.relationships.tracks.data).reduce((a, { attributes: { durationInMillis } }) => a + durationInMillis, 0) / 1000); - return `${app.showingPlaylist.relationships.tracks.data.length} ${app.getLz('term.track', options = { count: app.showingPlaylist.relationships.tracks.data.length })}, ${this.convertTime(timeInSeconds, 'long')}` - } else return "" - } catch (err) { - return "" - } - }, - async getLibrarySongs() { - let response = await this.mkapi("songs", true, "", { limit: 100, l: this.mklang }, { includeResponseMeta: !0 }) - this.library.songs.listing = response.data.data - this.library.songs.meta = response.data.meta - }, - async getLibraryAlbums() { - let response = await this.mkapi("albums", true, "", { limit: 100, l: this.mklang }, { includeResponseMeta: !0 }) - this.library.albums.listing = response.data.data - this.library.albums.meta = response.data.meta - }, - async getListenNow(attempt = 0) { - if (this.listennow.timestamp > Date.now() - 120000) { - return - } - - if (attempt > 3) { - return - } - try { - this.listennow = (await this.mk.api.v3.music(`v1/me/recommendations?timezone=${encodeURIComponent(this.formatTimezoneOffset())}`, { - name: "listen-now", - with: "friendsMix,library,social", - "art[social-profiles:url]": "c", - "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", - l: this.mklang - }, { - includeResponseMeta: !0, - reload: !0 - })).data; - this.listennow.timestamp = Date.now() - console.log(this.listennow) - } catch (e) { - console.log(e) - this.getListenNow(attempt + 1) - } - }, - async getBrowsePage(attempt = 0) { - if (this.browsepage.timestamp > Date.now() - 120000) { - return - } - if (attempt > 3) { - return - } - try { - let browse = await app.mk.api.v3.music(`/v1/editorial/${app.mk.storefrontId}/groupings`, { - platform: "web", - name: "music", - "omit[resource:artists]": "relationships", - "include[albums]": "artists", - "include[songs]": "artists", - "include[music-videos]": "artists", - extend: "editorialArtwork,artistUrl", - "fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes", - "art[url]": "f", - l: this.mklang - }); - this.browsepage = browse.data.data[0]; - this.browsepage.timestamp = Date.now() - console.log(this.browsepage) - } catch (e) { - console.log(e) - this.getBrowsePage(attempt + 1) - } - }, - async getRadioStations(attempt = 0) { - if (attempt > 3) { - return - } - try { - this.radio.personal = (await app.mk.api.v3.music(`/v1/me/recent/radio-stations`, { - "platform": "web", - "art[url]": "f", - l: this.mklang - })).data.data; - } catch (e) { - console.log(e) - this.getRadioStations(attempt + 1) - } - }, - async getMadeForYou(attempt = 0) { - if (attempt > 3) { - return - } - try { - mfu = await app.mk.api.v3.music("/v1/me/library/playlists?platform=web&extend=editorialVideo&fields%5Bplaylists%5D=lastModifiedDate&filter%5Bfeatured%5D=made-for-you&include%5Blibrary-playlists%5D=catalog&fields%5Blibrary-playlists%5D=artwork%2Cname%2CplayParams%2CdateAdded") - this.madeforyou = mfu.data - } catch (e) { - console.log(e) - this.getMadeForYou(attempt + 1) - } - }, - newPlaylistFolder(name = app.getLz('term.newPlaylistFolder')) { - let self = this - this.mk.api.v3.music( - "/v1/me/library/playlist-folders/", {}, { - fetchOptions: { - method: "POST", - body: JSON.stringify({ - attributes: { name: name } - }) - } - } - ).then((res) => { - let playlist = (res.data.data[0]) - self.playlists.listing.push({ - id: playlist.id, - attributes: { - name: playlist.attributes.name - }, - type: "library-playlist-folders", - parent: "p.playlistsroot" - }) - self.sortPlaylists() - setTimeout(() => { - app.refreshPlaylists() - }, 13000) - }) - }, - showSearch() { - this.page = "search" - }, - loadLyrics() { - const musicType = (MusicKit.getInstance().nowPlayingItem != null) ? MusicKit.getInstance().nowPlayingItem["type"] ?? '' : ''; - console.log("mt", musicType) - if (musicType === "musicVideo") { - this.loadYTLyrics(); - } else { - if (app.cfg.lyrics.enable_mxm) { - this.loadMXM(); - } else { - this.loadAMLyrics(); - } - } - }, - loadAMLyrics() { - const songID = (this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem["_songId"] ?? (this.mk.nowPlayingItem["songId"] ?? -1) : -1; - // this.getMXM( trackName, artistName, 'en', duration); - if (songID != -1) { - this.mk.api.v3.music(`v1/catalog/${this.mk.storefrontId}/songs/${songID}/lyrics`) - .then((response) => { - this.lyricsMediaItem = response.data?.data[0]?.attributes["ttml"] - this.parseTTML() - }) - } - }, - addToLibrary(id) { - let self = this - this.mk.addToLibrary(id).then((data) => { - self.getLibrarySongsFull(true) - }) - notyf.success(app.getLz('action.addToLibrary.success')); - }, - removeFromLibrary(kind, id) { - let self = this - let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; - app.mk.api.v3.music(`v1/me/library/${truekind}/${id.toString()}`, {}, { - fetchOptions: { - method: "DELETE" - } - }).then((data) => { - self.getLibrarySongsFull(true) - }) - notyf.success(app.getLz('action.removeFromLibrary.success')) - }, - async loadYTLyrics() { - const track = (this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.title ?? '' : ''; - const artist = (this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.artistName ?? '' : ''; - const time = (this.mk.nowPlayingItem != null) ? (Math.round((this.mk.nowPlayingItem.attributes["durationInMillis"] ?? -1000) / 1000) ?? -1) : -1; - ipcRenderer.invoke('getYTLyrics', track, artist).then((result) => { - if (result.length > 0) { - let ytid = result[0]['id']['videoId']; - if (app.cfg.lyrics.enable_yt) { - loadYT(ytid, app.cfg.lyrics.mxm_language ?? "en") - } else { - app.loadMXM() - } - } else { - app.loadMXM() - } - - function loadYT(id, lang) { - let req = new XMLHttpRequest(); - let url = `https://www.youtube.com/watch?&v=${id}`; - req.open('GET', url, true); - req.onerror = function (e) { - this.loadMXM(); - } - req.onload = function () { - // console.log(this.responseText); - res = this.responseText; - let captionurl1 = res.substring(res.indexOf(`{"playerCaptionsRenderer":{"baseUrl":"`) + (`{"playerCaptionsRenderer":{"baseUrl":"`).length); - let captionurl = captionurl1.substring(0, captionurl1.indexOf(`"`)); - if (captionurl.includes("timedtext")) { - let json = JSON.parse(`{"url": "${captionurl}"}`); - let newurl = json.url + `&lang=${lang}&format=ttml` - - let req2 = new XMLHttpRequest(); - - req2.open('GET', newurl, true); - req2.onerror = function (e) { - app.loadMXM(); - } - req2.onload = function () { - try { - const ttmlLyrics = this.responseText; - if (ttmlLyrics) { - this.lyricsMediaItem = ttmlLyrics - this.parseTTML() - } - } catch (e) { - app.loadMXM(); - } - - } - req2.send(); - } else { - - app.loadMXM(); - - } - } - req.send(); - } - - }) - - }, - loadMXM() { - let attempt = 0; - const track = encodeURIComponent((this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.title ?? '' : ''); - const artist = encodeURIComponent((this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.artistName ?? '' : ''); - const time = encodeURIComponent((this.mk.nowPlayingItem != null) ? (Math.round((this.mk.nowPlayingItem.attributes["durationInMillis"] ?? -1000) / 1000) ?? -1) : -1); - const id = encodeURIComponent((this.mk.nowPlayingItem != null) ? app.mk.nowPlayingItem._songId ?? (app.mk.nowPlayingItem["songId"] ?? '') : ''); - let lrcfile = ""; - let richsync = []; - const lang = app.cfg.lyrics.mxm_language // translation language - function revisedRandId() { - return Math.random().toString(36).replace(/[^a-z]+/g, '').slice(2, 10); - } - - /* get token */ - function getToken(mode, track, artist, songid, lang, time, id) { - if (attempt > 2) { - app.loadAMLyrics(); - } else { - attempt = attempt + 1; - let url = "https://apic-desktop.musixmatch.com/ws/1.1/token.get?app_id=web-desktop-app-v1.0&t=" + revisedRandId(); - let req = new XMLHttpRequest(); - req.overrideMimeType("application/json"); - req.open('GET', url, true); - req.setRequestHeader("authority", "apic-desktop.musixmatch.com"); - req.onload = function () { - let jsonResponse = JSON.parse(this.responseText); - let status2 = jsonResponse["message"]["header"]["status_code"]; - if (status2 == 200) { - let token = jsonResponse["message"]["body"]["user_token"] ?? ''; - if (token != "" && token != "UpgradeOnlyUpgradeOnlyUpgradeOnlyUpgradeOnly") { - console.log('200 token', mode); - // token good - app.mxmtoken = token; - - if (mode == 1) { - getMXMSubs(track, artist, app.mxmtoken, lang, time, id); - } else { - getMXMTrans(songid, lang, app.mxmtoken); - } - } else { - console.log('fake 200 token'); - getToken(mode, track, artist, songid, lang, time) - } - } else { - console.log('token 4xx'); - getToken(mode, track, artist, songid, lang, time) - } - - }; - req.onerror = function () { - console.log('error'); - app.loadAMLyrics(); - }; - req.send(); - } - } - - function getMXMSubs(track, artist, token, lang, time, id) { - let usertoken = encodeURIComponent(token); - let richsyncQuery = (app.cfg.lyrics.mxm_karaoke) ? "&optional_calls=track.richsync" : "" - let timecustom = (!time || (time && time < 0)) ? '' : `&f_subtitle_length=${time}&q_duration=${time}&f_subtitle_length_max_deviation=40`; - let itunesid = (id && id != "") ? `&track_itunes_id=${id}` : ''; - let url = "https://apic-desktop.musixmatch.com/ws/1.1/macro.subtitles.get?format=json&namespace=lyrics_richsynched" + richsyncQuery + "&subtitle_format=lrc&q_artist=" + artist + "&q_track=" + track + itunesid + "&usertoken=" + usertoken + timecustom + "&app_id=web-desktop-app-v1.0&t=" + revisedRandId(); - let req = new XMLHttpRequest(); - req.overrideMimeType("application/json"); - req.open('GET', url, true); - req.setRequestHeader("authority", "apic-desktop.musixmatch.com"); - req.onload = function () { - let jsonResponse = JSON.parse(this.responseText); - console.log(jsonResponse); - let status1 = jsonResponse["message"]["header"]["status_code"]; - - if (status1 == 200) { - let id = ''; - try { - if (jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["header"]["status_code"] == 200 && jsonResponse["message"]["body"]["macro_calls"]["track.subtitles.get"]["message"]["header"]["status_code"] == 200) { - id = jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["body"]["track"]["track_id"] ?? ''; - lrcfile = jsonResponse["message"]["body"]["macro_calls"]["track.subtitles.get"]["message"]["body"]["subtitle_list"][0]["subtitle"]["subtitle_body"]; - - try { - lrcrich = jsonResponse["message"]["body"]["macro_calls"]["track.richsync.get"]["message"]["body"]["richsync"]["richsync_body"]; - richsync = JSON.parse(lrcrich); - app.richlyrics = richsync; - } catch (_) { - } - } - - if (lrcfile == "") { - app.loadAMLyrics() - } else { - if (richsync == [] || richsync.length == 0) { - console.log("ok"); - // process lrcfile to json here - app.lyricsMediaItem = lrcfile - let u = app.lyricsMediaItem.split(/[\r\n]/); - let preLrc = [] - for (var i = u.length - 1; i >= 0; i--) { - let xline = (/(\[[0-9.:\[\]]*\])+(.*)/).exec(u[i]) - let end = (preLrc.length > 0) ? ((preLrc[preLrc.length - 1].startTime) ?? 99999) : 99999 - preLrc.push({ - startTime: app.toMS(xline[1].substring(1, xline[1].length - 2)) ?? 0, - endTime: end, - line: xline[2], - translation: '' - }) - } - if (preLrc.length > 0) - preLrc.push({ - startTime: 0, - endTime: preLrc[preLrc.length - 1].startTime, - line: "lrcInstrumental", - translation: '' - }); - app.lyrics = preLrc.reverse(); - } else { - preLrc = richsync.map(function (item) { - return { - startTime: item.ts, - endTime: item.te, - line: item.x, - translation: '' - } - }) - if (preLrc.length > 0) - preLrc.unshift({ - startTime: 0, - endTime: preLrc[0].startTime, - line: "lrcInstrumental", - translation: '' - }); - app.lyrics = preLrc; - } - if (lrcfile != null && lrcfile != '' && lang != "disabled") { - // load translation - getMXMTrans(id, lang, token); - } else { - app.loadAMLyrics() - } - } - } catch (e) { - console.log(e); - app.loadAMLyrics() - } - } else { //4xx rejected - getToken(1, track, artist, '', lang, time); - } - } - req.onerror = function () { - console.log('error'); - app.loadAMLyrics(); - }; - req.send(); - } - - function getMXMTrans(id, lang, token) { - if (lang != "disabled" && id != '') { - let usertoken = encodeURIComponent(token); - let url2 = "https://apic-desktop.musixmatch.com/ws/1.1/crowd.track.translations.get?translation_fields_set=minimal&selected_language=" + lang + "&track_id=" + id + "&comment_format=text&part=user&format=json&usertoken=" + usertoken + "&app_id=web-desktop-app-v1.0&t=" + revisedRandId(); - let req2 = new XMLHttpRequest(); - req2.overrideMimeType("application/json"); - req2.open('GET', url2, true); - req2.setRequestHeader("authority", "apic-desktop.musixmatch.com"); - req2.onload = function () { - let jsonResponse2 = JSON.parse(this.responseText); - console.log(jsonResponse2); - let status2 = jsonResponse2["message"]["header"]["status_code"]; - if (status2 == 200) { - try { - let preTrans = [] - let u = app.lyrics; - let translation_list = jsonResponse2["message"]["body"]["translations_list"]; - if (translation_list.length > 0) { - for (var i = 0; i < u.length - 1; i++) { - preTrans[i] = "" - for (var trans_line of translation_list) { - if (u[i].line == " " + trans_line["translation"]["matched_line"] || u[i].line == trans_line["translation"]["matched_line"]) { - u[i].translation = trans_line["translation"]["description"]; - break; - } - } - } - app.lyrics = u; - } - } catch (e) { - /// not found trans -> ignore - } - } else { //4xx rejected - getToken(2, '', '', id, lang, ''); - } - } - req2.send(); - } - - } - - if (track != "" & track != "No Title Found") { - if (app.mxmtoken != null && app.mxmtoken != '') { - getMXMSubs(track, artist, app.mxmtoken, lang, time, id) - } else { - getToken(1, track, artist, '', lang, time); - } - } - }, - toMS(str) { - let rawTime = str.match(/(\d+:)?(\d+:)?(\d+)(\.\d+)?/); - let hours = (rawTime[2] != null) ? (rawTime[1].replace(":", "")) : 0; - let minutes = (rawTime[2] != null) ? (hours * 60 + rawTime[2].replace(":", "") * 1) : ((rawTime[1] != null) ? rawTime[1].replace(":", "") : 0); - let seconds = (rawTime[3] != null) ? (rawTime[3]) : 0; - let milliseconds = (rawTime[4] != null) ? (rawTime[4].replace(".", "")) : 0 - return parseFloat(`${minutes * 60 + seconds * 1}.${milliseconds * 1}`); - }, - parseTTML() { - this.lyrics = []; - let preLrc = []; - let xml = this.stringToXml(this.lyricsMediaItem); - let lyricsLines = xml.getElementsByTagName('p'); - let synced = true; - let endTimes = []; - if (xml.getElementsByTagName('tt')[0].getAttribute("itunes:timing") === "None") { - synced = false; - } - endTimes.push(0); - if (synced) { - for (element of lyricsLines) { - start = this.toMS(element.getAttribute('begin')) - end = this.toMS(element.getAttribute('end')) - if (start - endTimes[endTimes.length - 1] > 5 && endTimes[endTimes.length - 1] != 0) { - preLrc.push({ - startTime: endTimes[endTimes.length - 1], - endTime: start, - line: "lrcInstrumental" - }); - } - preLrc.push({ startTime: start, endTime: end, line: element.textContent }); - endTimes.push(end); - } - // first line dot - if (preLrc.length > 0) - preLrc.unshift({ startTime: 0, endTime: preLrc[0].startTime, line: "lrcInstrumental" }); - } else { - for (element of lyricsLines) { - preLrc.push({ startTime: 9999999, endTime: 9999999, line: element.textContent }); - } - } - this.lyrics = preLrc; - - }, - parseLyrics() { - let xml = this.stringToXml(this.lyricsMediaItem) - let json = xmlToJson(xml); - this.lyrics = json - }, - stringToXml(st) { - // string to xml - let xml = (new DOMParser()).parseFromString(st, "text/xml"); - return xml; - - }, - getCurrentTime() { - return parseFloat(this.hmsToSecondsOnly(this.parseTime(this.mk.nowPlayingItem.attributes.durationInMillis - app.mk.currentPlaybackTimeRemaining * 1000))); - }, - seekTo(time) { - this.mk.seekToTime(time); - }, - parseTime(value) { - let minutes = Math.floor(value / 60000); - let seconds = ((value % 60000) / 1000).toFixed(0); - return minutes + ":" + (seconds < 10 ? '0' : '') + seconds; - }, - parseTimeDecimal(value) { - let minutes = Math.floor(value / 60000); - let seconds = ((value % 60000) / 1000).toFixed(0); - return minutes + "." + (seconds < 10 ? '0' : '') + seconds; - }, - hmsToSecondsOnly(str) { - let p = str.split(':'), - s = 0, - m = 1; - - while (p.length > 0) { - s += m * parseInt(p.pop(), 10); - m *= 60; - } - - return s; - }, - getLyricBGStyle(start, end) { - let currentTime = this.getCurrentTime(); - // let duration = this.mk.nowPlayingItem.attributes.durationInMillis - let start2 = this.hmsToSecondsOnly(start) - let end2 = this.hmsToSecondsOnly(end) - // let 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 {} - } - }, - playMediaItemById(id, kind, isLibrary, raurl = "") { - let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; - console.log(id, truekind, isLibrary) - try { - if (truekind.includes("artist")) { - app.mk.setStationQueue({ artist: 'a-' + id }).then(() => { - app.mk.play() - }) - } else if (truekind == "radioStations") { - this.mk.setStationQueue({ url: raurl }).then(function (queue) { - MusicKit.getInstance().play() - }); - } else { - this.mk.setQueue({ - [truekind]: [id], - parameters: { l: this.mklang } - }).then(function (queue) { - MusicKit.getInstance().play() - }) - } - } catch (err) { - console.log(err) - this.playMediaItemById(id, kind, isLibrary, raurl) - } - }, - queueParentandplayChild(parent, childIndex, item) { - - /* Randomize array in-place using Durstenfeld shuffle algorithm */ - function shuffleArray(array) { - for (var i = array.length - 1; i > 0; i--) { - var j = Math.floor(Math.random() * (i + 1)); - var temp = array[i]; - array[i] = array[j]; - array[j] = temp; - } - } - - let kind = parent.substring(0, parent.indexOf(":")) - let id = parent.substring(parent.indexOf(":") + 1, parent.length) - let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; - console.log(truekind, id) - - try { - if (app.library.songs.displayListing.length > childIndex && parent == "librarysongs") { - console.log(item) - if (item && ((app.library.songs.displayListing[childIndex].id != item.id))) { - childIndex = app.library.songs.displayListing.indexOf(item) - } - - let query = app.library.songs.displayListing.map(item => new MusicKit.MediaItem(item)); - - - app.mk.stop().then(() => { - if (item) { - app.mk.setQueue({ - [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, - parameters: { l: app.mklang } - }).then(function () { - app.mk.play().then(() => { - if (app.mk.shuffleMode == 1) { - shuffleArray(query) - } else { - for (let i = 0; i < query.length; i++) { - if (query[i].id == item.id) { - query.splice(0, i + 1); - break; - } - } - } - app.mk.queue.append(query) - }) - }) - } else { - app.mk.queue.splice(0, app.mk.queue._itemIDs.length) - if (app.mk.shuffleMode == 1) { - shuffleArray(query) - } - app.mk.queue.append(query) - if (childIndex != -1) { - app.mk.changeToMediaAtIndex(childIndex) - } else { - app.mk.play() - } - - } - }) - } else if (parent.startsWith('listitem-hr')) { - app.mk.stop().then(() => { - if (app.mk.shuffleMode == 1) { - app.mk.setQueue({ - [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id - }).then(function () { - app.mk.play().then(() => { - let data = JSON.parse(parent.split('listitem-hr')[1] ?? '[]') - let itemsToPlay = {} - let u = data.map(x => x.id) - try { - data.splice(u.indexOf(item.attributes.playParams.id ?? item.id), 1) - } catch (e) { - } - if (app.mk.shuffleMode == 1) { - shuffleArray(data) - } - data.forEach(item => { - if (!itemsToPlay[item.kind]) { - itemsToPlay[item.kind] = [] - } - itemsToPlay[item.kind].push(item.id) - }) - // loop through itemsToPlay - for (let kind in itemsToPlay) { - let ids = itemsToPlay[kind] - if (ids.length > 0) { - app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] }) - } - } - }) - }) - } else { - let data = JSON.parse(parent.split('listitem-hr')[1] ?? '[]') - let itemsToPlay = {} - data.forEach(item => { - if (!itemsToPlay[item.kind]) { - itemsToPlay[item.kind] = [] - } - itemsToPlay[item.kind].push(item.id) - }) - // loop through itemsToPlay - app.mk.queue.splice(0, app.mk.queue._itemIDs.length) - let ind = 0; - for (let kind in itemsToPlay) { - let ids = itemsToPlay[kind] - if (ids.length > 0) { - if (app.mk.queue._itemIDs.length > 0) { - app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] }).then(function () { - ind += 1; - console.log(ind, Object.keys(itemsToPlay).length) - if (ind >= Object.keys(itemsToPlay).length) { - app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.attributes.playParams.id ?? item.id)) - } - } - ) - } else { - app.mk.setQueue({ [kind + "s"]: itemsToPlay[kind] }).then(function () { - ind += 1; - console.log(ind, Object.keys(itemsToPlay).length) - if (ind >= Object.keys(itemsToPlay).length) { - app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.attributes.playParams.id ?? item.id)) - } - } - ) - } - } - - } - } - }) - } else { - app.mk.stop().then(() => { - if (truekind == "playlists" && (id.startsWith("p.") || id.startsWith("pl.u"))) { - app.mk.setQueue({ - [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, - parameters: { l: app.mklang } - }).then(function () { - app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1).then(function () { - if ((app.showingPlaylist && app.showingPlaylist.id == id)) { - let query = app.showingPlaylist.relationships.tracks.data.map(item => new MusicKit.MediaItem(item)); - let u = query; - if (app.mk.shuffleMode == 1) { - shuffleArray(u) - } else { - for (let i = 0; i < app.showingPlaylist.relationships.tracks.data.length; i++) { - if (app.showingPlaylist.relationships.tracks.data[i].id == item.id) { - u.splice(0, i + 1); - break; - } - } - } - app.mk.queue.append(u) - } else { - app.getPlaylistFromID(id, true).then(function () { - let query = app.showingPlaylist.relationships.tracks.data.map(item => new MusicKit.MediaItem(item)); - let u = query; - if (app.mk.shuffleMode == 1) { - shuffleArray(u) - } else { - for (let i = 0; i < app.showingPlaylist.relationships.tracks.data.length; i++) { - if (app.showingPlaylist.relationships.tracks.data[i].id == item.id) { - u.splice(0, i + 1); - break; - } - } - } - app.mk.queue.append(u) - }) - } - }) - - }) - } else { - this.mk.setQueue({ - [truekind]: [id], - parameters: { l: this.mklang } - }).then(function (queue) { - if (item && ((queue._itemIDs[childIndex] != item.id))) { - childIndex = queue._itemIDs.indexOf(item.id) - } - if (childIndex != -1) { - app.mk.changeToMediaAtIndex(childIndex) - } else if (item) { - app.mk.playNext({ - [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id - }).then(function () { - app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1) - app.mk.play() - }) - } else { - app.mk.play() - } - }) - } - }) - } - } catch (err) { - console.log(err) - try { - app.mk.stop() - } catch (e) { - } - this.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url) - } - - }, - friendlyTypes(type) { - // use switch statement to return friendly name for media types "songs,artists,albums,playlists,music-videos,stations,apple-curators,curators" - switch (type) { - case "song": - return app.getLz('term.songs') - break; - case "artist": - return app.getLz('term.artists') - break; - case "album": - return app.getLz('term.albums') - break; - case "playlist": - return app.getLz('term.playlists') - break; - case "music_video": - return app.getLz('term.musicVideos') - break; - case "station": - return app.getLz('term.stations') - break; - case "apple-curator": - return app.getLz('term.appleCurators') - break; - case "radio_show": - return app.getLz('term.radioShows') - break; - case "record_label": - return app.getLz('term.recordLabels') - break; - case "radio_episode": - return app.getLz('podcast.episodes') - break; - case "video_extra": - return app.getLz('term.videoExtras') - break; - case "curator": - return app.getLz('term.curators') - break; - case "top": - return app.getLz('term.top') - break; - default: - return type - break; - } - }, - async searchQuery(term = this.search.term) { - let self = this - if (term == "") { - return - } - //this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${this.search.term}` - this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${encodeURIComponent(this.search.term)}`, { - types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels", - "relate[editorial-items]": "contents", - "include[editorial-items]": "contents", - "include[albums]": "artists", - "include[artists]": "artists", - "include[songs]": "artists,albums", - "include[music-videos]": "artists", - "extend": "artistUrl", - "fields[artists]": "url,name,artwork,hero", - "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url", - "with": "serverBubbles,lyricHighlights", - "art[url]": "c,f", - "omit[resource]": "autos", - "platform": "web", - limit: 25, - l: this.mklang - }).then(function (results) { - results.data.results["meta"] = results.data.meta - self.search.results = results.data.results - }) - - await app.mk.api.v3.music(`v1/social/${app.mk.storefrontId}/search?term=${app.search.term}`, { - types: ["playlists", "social-profiles"], - limit: 25, - with: ["serverBubbles", "lyricSnippet"], - "art[url]": "f", - "art[social-profiles:url]": "c" - }, { includeResponseMeta: !0 }).then(function (results) { - results.data.results["meta"] = results.data.meta - self.search.resultsSocial = results.data.results - }) - }, - async inLibrary(items = []) { - let types = [] - - for (let item of items) { - let type = item.type - if (type.slice(-1) != "s") { - type += "s" - } - type = type.replace("library-", "") - let id = item.attributes.playParams.catalogId ?? item.attributes.playParams.id ?? item.id - - let index = types.findIndex(function (type) { - return type.type == this - }, type) - if (index == -1) { - types.push({ type: type, id: [id] }) - } else { - types[index].id.push(id) - } - } - types2 = types.map(function (item) { - return { - [`ids[${item.type}]`]: [item.id] - } - }) - types2 = types2.reduce(function (result, item) { - var key = Object.keys(item)[0]; //first property: a, b, c - result[key] = item[key]; - return result; - }, {}); - return (await this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}`, { - ...{ - "omit[resource]": "autos", - relate: "library", - fields: "inLibrary" - }, - ...types2 - })).data.data - }, - isInLibrary(playParams) { - let self = this - let id = "" - // ugly code to check if current playback item is in library - if (typeof playParams == "undefined") { - return true - } - if (playParams["isLibrary"]) { - return true - } else if (playParams["catalogId"]) { - id = playParams["catalogId"] - } else if (playParams["id"]) { - id = playParams["id"] - } - let found = this.library.songs.listing.filter((item) => { - if (item["attributes"]) { - if (item["attributes"]["playParams"] && (item["attributes"]["playParams"]["catalogId"] == id)) { - return item; - } - } - }) - if (found.length != 0) { - return true - } else { - return false - } - }, - mkReady() { - if (this.mk["nowPlayingItem"]) { - return true - } else { - return false - } - }, - getMediaItemArtwork(url, height = 64, width) { - if (typeof url == "undefined" || url == "") { - return "https://beta.music.apple.com/assets/product/MissingArtworkMusic.svg" - } - let newurl = `${url.replace('{w}', width ?? height).replace('{h}', height).replace('{f}', "webp").replace('{c}', ((width === 900) ? "sr" : "cc"))}`; - - if (newurl.includes("900x516")) { - newurl = newurl.replace("900x516cc", "900x516sr").replace("900x516bb", "900x516sr"); - } - return newurl - }, - _rgbToRgb(rgb = [0, 0, 0]) { - // if rgb - return `rgb(${rgb[0]},${rgb[1]},${rgb[2]})` - }, - getNowPlayingArtworkBG(size = 32, force = false) { - let self = this - if (typeof this.mk.nowPlayingItem === "undefined") return; - let bginterval = setInterval(() => { - if (!this.mkReady()) { - return "" - } - - try { - if ((this.mk.nowPlayingItem && this.mk.nowPlayingItem["id"] != this.currentTrackID && document.querySelector('.bg-artwork')) || force) { - if (document.querySelector('.bg-artwork')) { - clearInterval(bginterval); - } - this.currentTrackID = this.mk.nowPlayingItem["id"]; - document.querySelector('.bg-artwork').src = ""; - if (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"]) { - getBase64FromUrl(this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"].replace('{w}', size).replace('{h}', size)).then(img => { - document.querySelectorAll('.bg-artwork').forEach(artwork => { - artwork.src = img; - }) - self.$store.commit("setLCDArtwork", img) - }) - try { - clearInterval(bginterval); - } catch (err) { - } - } else { - this.setLibraryArtBG() - } - } else if (this.mk.nowPlayingItem["id"] == this.currentTrackID) { - try { - clearInterval(bginterval); - } catch (err) { - } - } - } catch (e) { - if (this.mk.nowPlayingItem && this.mk.nowPlayingItem["id"] && document.querySelector('.bg-artwork')) { - this.setLibraryArtBG() - try { - clearInterval(bginterval); - } catch (err) { - } - } - } - }, 200) - }, - async getCurrentArtURL() { - try { - let artworkSize = 50 - if (app.getThemeDirective("lcdArtworkSize") != "") { - artworkSize = app.getThemeDirective("lcdArtworkSize") - } else if (this.cfg.visual.directives.windowLayout == "twopanel") { - artworkSize = 70 - } - this.currentArtUrl = ''; - this.currentArtUrlRaw = ''; - if (app.mk.nowPlayingItem != null && app.mk.nowPlayingItem.attributes != null && app.mk.nowPlayingItem.attributes.artwork != null && app.mk.nowPlayingItem.attributes.artwork.url != null && app.mk.nowPlayingItem.attributes.artwork.url != '') { - this.currentArtUrlRaw = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '') - this.currentArtUrl = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '').replace('{w}', artworkSize).replace('{h}', artworkSize); - try { - document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); - } catch (e) { - } - } else { - 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) { - this.currentArtUrlRaw = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '') - this.currentArtUrl = (data["attributes"]["artwork"]["url"] ?? '').replace('{w}', artworkSize).replace('{h}', artworkSize); - ipcRenderer.send('updateRPCImage', this.currentArtUrl ?? ''); - try { - document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); - } catch (e) { - } - } else { - this.currentArtUrlRaw = '' - this.currentArtUrl = ''; - try { - document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); - } catch (e) { - } - } - } - } catch (e) { - - } - }, - async setLibraryArt() { - if (typeof this.mk.nowPlayingItem === "undefined") return; - try { - 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 !== "") { - document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', 'url("' + (data["attributes"]["artwork"]["url"]).toString() + '")'); - } else { - document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("")`); - } - } catch (e) { - } - }, - async setLibraryArtBG() { - if (typeof this.mk.nowPlayingItem === "undefined") return; - try { - 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 !== "") { - getBase64FromUrl((data["attributes"]["artwork"]["url"]).toString()).then(img => { - document.querySelector('.bg-artwork').forEach(artwork => { - artwork.src = img; - }) - self.$store.commit("setLCDArtwork", img) - }) - } - } catch (e) { - } - - }, - quickPlay(query) { - let self = this - MusicKit.getInstance().api.search(query, { limit: 2, types: 'songs' }).then(function (data) { - MusicKit.getInstance().setQueue({ - song: data["songs"]['data'][0]["id"], - parameters: { l: app.mklang } - }).then(function (queue) { - MusicKit.getInstance().play() - setTimeout(() => { - self.$forceUpdate() - }, 1000) - }) - }) - }, - async getRating(item) { - let type = item.type.slice(-1) === "s" ? item.type : item.type + "s" - let id = item.attributes?.playParams?.catalogId ? item.attributes.playParams.catalogId : (item.attributes?.playParams?.id ?? item.id) - if (item.id != null && (item.id.toString()).startsWith("i.")) { - if (!type.startsWith("library-")) { - type = "library-" + type - } - id = item.id - } - let response = await this.mk.api.v3.music(`/v1/me/ratings/${type}?platform=web&ids=${type.includes('library') ? item.id : id}`) - if (response.data.data.length != 0) { - let value = response.data.data[0].attributes.value - return value - } else { - return 0 - } - }, - love(item) { - let type = item.type.slice(-1) === "s" ? item.type : item.type + "s" - let id = item.attributes?.playParams?.catalogId ? item.attributes.playParams.catalogId : (item.attributes?.playParams?.id ?? item.id) - if (item.id != null && (item.id.toString()).startsWith("i.")) { - if (!type.startsWith("library-")) { - type = "library-" + type - } - id = item.id - } - this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, { - fetchOptions: { - method: "PUT", - body: JSON.stringify({ - "type": "rating", - "attributes": { - "value": 1 - } - }) - } - }) - }, - dislike(item) { - let type = item.type.slice(-1) === "s" ? item.type : item.type + "s" - let id = item.attributes?.playParams?.catalogId ? item.attributes.playParams.catalogId : (item.attributes?.playParams?.id ?? item.id) - if (item.id != null && (item.id.toString()).startsWith("i.")) { - if (!type.startsWith("library-")) { - type = "library-" + type - } - id = item.id - } - this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, { - fetchOptions: { - method: "PUT", - body: JSON.stringify({ - "type": "rating", - "attributes": { - "value": -1 - } - }) - } - }) - }, - unlove(item) { - let type = item.type.slice(-1) === "s" ? item.type : item.type + "s" - let id = item.attributes.playParams.catalogId ? item.attributes.playParams.catalogId : item.id - if (item.id.startsWith("i.")) { - if (!type.startsWith("library-")) { - type = "library-" + type - } - id = item.id - } - this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, { - fetchOptions: { - method: "DELETE", - } - }) - }, - checkScrollDirectionIsUp(event) { - if (event.wheelDelta) { - return event.wheelDelta > 0; - } - return event.deltaY < 0; - }, - volumeUp() { - if ((app.mk.volume + app.cfg.audio.volumeStep) > 1) { - app.mk.volume = app.cfg.audio.maxVolume; - console.log('setting max volume') - } else { - console.log('volume up') - app.mk.volume = ((app.mk.volume * 100) + (app.cfg.audio.volumeStep * 100)) / 100 - } - }, - volumeDown() { - if ((app.mk.volume - app.cfg.audio.volumeStep) < 0) { - app.mk.volume = 0; - console.log('setting volume to 0') - } else { - console.log('volume down') - app.mk.volume = ((app.mk.volume * 100) - (app.cfg.audio.volumeStep * 100)) / 100 - } - }, - volumeWheel(event) { - app.checkScrollDirectionIsUp(event) ? this.volumeUp() : this.volumeDown() - }, - muteButtonPressed() { - if (this.cfg.audio.muted) { - this.mk.volume = this.cfg.audio.lastVolume; - this.cfg.audio.muted = false; - } else { - this.cfg.audio.lastVolume = this.cfg.audio.volume; - this.mk.volume = 0; - this.cfg.audio.muted = true; - } - }, - checkMuteChange() { - if (this.cfg.audio.muted) { - this.cfg.audio.muted = false; - } - }, - async apiCall(url, callback) { - const xmlHttp = new XMLHttpRequest(); - - xmlHttp.onreadystatechange = (e) => { - if (xmlHttp.readyState !== 4) { - return; - } - - if (xmlHttp.status === 200) { - // console.log('SUCCESS', xmlHttp.responseText); - callback(JSON.parse(xmlHttp.responseText)); - } else { - console.warn('request_error'); - } - }; - - xmlHttp.open("GET", url); - xmlHttp.setRequestHeader("Authorization", "Bearer " + MusicKit.getInstance().developerToken); - xmlHttp.setRequestHeader("Music-User-Token", "" + MusicKit.getInstance().musicUserToken); - xmlHttp.setRequestHeader("Accept", "application/json"); - xmlHttp.setRequestHeader("Content-Type", "application/json"); - xmlHttp.responseType = "text"; - xmlHttp.send(); - }, - fetchPlaylist(id, callback) { - // id can be found in playlist.attributes.playParams.globalId - // this.mk.api. - 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 - }, - windowFocus(val) { - if (val) { - document.querySelectorAll(".animated-artwork-video").forEach(el => { - el.play() - }) - document.querySelector("body").classList.remove("stopanimation") - this.animateBackground = true - } else { - document.querySelectorAll(".animated-artwork-video").forEach(el => { - el.pause() - }) - document.querySelector("body").classList.add("stopanimation") - this.animateBackground = false - } - }, - async nowPlayingContextMenu(event) { - let self = this - let data_type = this.mk.nowPlayingItem.playParams.kind - let item_id = this.mk.nowPlayingItem.attributes.playParams.id ?? this.mk.nowPlayingItem.id - let isLibrary = this.mk.nowPlayingItem.attributes.playParams.isLibrary ?? false - let params = { "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "t": "1" } - app.selectedMediaItems = [] - app.select_selectMediaItem(item_id, data_type, 0, '12344', isLibrary) - let useMenu = "normal" - let menus = { - multiple: { - items: [] - }, - normal: { - headerItems: [{ - "icon": "./assets/feather/heart.svg", - "id": "love", - "name": app.getLz('action.love'), - "hidden": false, - "disabled": true, - "action": function () { - app.love(app.mk.nowPlayingItem) - } - }, - { - "icon": "./assets/feather/heart.svg", - "id": "unlove", - "active": true, - "name": app.getLz('action.unlove'), - "hidden": true, - "action": function () { - app.unlove(app.mk.nowPlayingItem) - } - }, - { - "icon": "./assets/feather/thumbs-down.svg", - "id": "dislike", - "name": app.getLz('action.dislike'), - "hidden": false, - "disabled": true, - "action": function () { - app.dislike(app.mk.nowPlayingItem) - } - }, - { - "icon": "./assets/feather/thumbs-down.svg", - "id": "undo_dislike", - "name": app.getLz('action.undoDislike'), - "active": true, - "hidden": true, - "action": function () { - app.unlove(app.mk.nowPlayingItem) - } - }, - ], - items: [ - { - "icon": "./assets/feather/plus.svg", - "id": "addToLibrary", - "name": app.getLz('action.addToLibrary') + " ...", - "disabled": true, - "action": function () { - app.addToLibrary(app.mk.nowPlayingItem.id); - } - }, - { - "id": "removeFromLibrary", - "icon": "./assets/feather/x-circle.svg", - "name": app.getLz('action.removeFromLibrary'), - "hidden": true, - "action": function () { - self.removeFromLibrary() - } - }, - { - "icon": "./assets/feather/list.svg", - "name": app.getLz('action.addToPlaylist') + " ...", - "action": function () { - app.promptAddToPlaylist() - } - }, - { - "icon": "./assets/feather/radio.svg", - "name": app.getLz('action.startRadio'), - "action": function () { - app.mk.setStationQueue({ song: app.mk.nowPlayingItem.id }).then(() => { - app.mk.play() - app.selectedMediaItems = [] - }) - } - }, - { - "icon": "./assets/feather/user.svg", - "name": app.getLz('action.goToArtist'), - "action": function () { - app.appRoute(`artist/${app.mk.nowPlayingItem.relationships.artists.data[0].id}`) - } - }, - { - "icon": "./assets/feather/disc.svg", - "name": app.getLz('action.goToAlbum'), - "action": function () { - app.appRoute(`album/${app.mk.nowPlayingItem.relationships.albums.data[0].id}`) - } - }, - { - "icon": "./assets/feather/share.svg", - "name": app.getLz('action.share'), - "action": function () { - app.mkapi(app.mk.nowPlayingItem.attributes?.playParams?.kind ?? app.mk.nowPlayingItem.type ?? 'songs', false, app.mk.nowPlayingItem._songId ?? (app.mk.nowPlayingItem.songId ?? app.mk.nowPlayingItem.id) ?? '').then(u => { - app.copyToClipboard((u.data.data.length && u.data.data.length > 0) ? u.data.data[0].attributes.url : u.data.data.attributes.url) - }) - } - }, - { - "icon": "./assets/feather/share.svg", - "name": `${app.getLz('action.share')} (song.link)`, - "action": function () { - app.mkapi(app.mk.nowPlayingItem.attributes?.playParams?.kind ?? app.mk.nowPlayingItem.type ?? 'songs', false, app.mk.nowPlayingItem._songId ?? (app.mk.nowPlayingItem.songId ?? app.mk.nowPlayingItem.id) ?? '').then(u => { - app.songLinkShare((u.data.data.length && u.data.data.length > 0) ? u.data.data[0].attributes.url : u.data.data.attributes.url) - }) - } - } - ] - } - } - if (this.contextExt) { - if (this.contextExt.normal) { - menus.normal.items = menus.normal.items.concat(this.contextExt.normal) - } - } - this.showMenuPanel(menus[useMenu], event) - - try { - let result = await this.inLibrary([this.mk.nowPlayingItem]) - if (result[0].attributes.inLibrary) { - menus.normal.items.find(x => x.id == 'addToLibrary').hidden = true - menus.normal.items.find(x => x.id == 'removeFromLibrary').hidden = false - } else { - menus.normal.items.find(x => x.id == 'addToLibrary').disabled = false - } - } catch (e) { - e = null - } - - try { - let rating = await app.getRating(app.mk.nowPlayingItem) - if (rating == 0) { - menus.normal.headerItems.find(x => x.id == 'love').disabled = false - menus.normal.headerItems.find(x => x.id == 'dislike').disabled = false - } else if (rating == 1) { - menus.normal.headerItems.find(x => x.id == 'unlove').hidden = false - menus.normal.headerItems.find(x => x.id == 'love').hidden = true - } else if (rating == -1) { - menus.normal.headerItems.find(x => x.id == 'undo_dislike').hidden = false - menus.normal.headerItems.find(x => x.id == 'dislike').hidden = true - } - } catch (err) { - - } - }, - LastFMDeauthorize() { - ipcRenderer.invoke('setStoreValue', 'lastfm.enabled', false).catch((e) => console.error(e)); - ipcRenderer.invoke('setStoreValue', 'lastfm.auth_token', '').catch((e) => console.error(e)); - app.cfg.lastfm.auth_token = ""; - app.cfg.lastfm.enabled = false; - const element = document.getElementById('lfmConnect'); - element.innerHTML = app.getLz('term.connect'); - element.onclick = app.LastFMAuthenticate; - }, - LastFMAuthenticate() { - console.log("[LastFM] Received LastFM authentication callback") - const element = document.getElementById('lfmConnect'); - // new key : f9986d12aab5a0fe66193c559435ede3 - window.open('https://www.last.fm/api/auth?api_key=f9986d12aab5a0fe66193c559435ede3&cb=cider://auth/lastfm'); - element.innerText = app.getLz('term.connecting') + '...'; - - /* Just a timeout for the button */ - setTimeout(() => { - if (element.innerText === app.getLz('term.connecting') + '...') { - element.innerText = app.getLz('term.connect'); - console.warn('[LastFM] Attempted connection timed out.'); - } - }, 20000); - - ipcRenderer.on('LastfmAuthenticated', function (_event, lfmAuthKey) { - app.cfg.lastfm.auth_token = lfmAuthKey; - app.cfg.lastfm.enabled = true; - element.innerHTML = `${app.getLz('term.disconnect')}\n

(${app.getLz('term.authed')}: ${lfmAuthKey})

`; - element.onclick = app.LastFMDeauthorize; - }); - }, - /** - parseSCTagToRG: function (tag) { - let soundcheck = tag.split(" ") - let numbers = [] - for (item of soundcheck) { - numbers.push(parseInt(item, 16)) - - } - numbers.shift() - //let gain = Math.log10((Math.max(numbers[0], numbers[1]) ?? 1000) / 1000.0) * -10 - let peak = Math.max(numbers[6], numbers[7]) / 32768.0 - let gain = Math.pow(10, ((-7.63 - (Math.log10(peak) * 20)) / 20))// EBU R 128 Compliant - return { - gain: gain, - peak: peak - } - },*/ - fullscreen(flag) { - if (flag) { - ipcRenderer.send('setFullScreen', true); - if (app.mk.nowPlayingItem.type && app.mk.nowPlayingItem.type.toLowerCase().includes("video")) { - document.querySelector('video#apple-music-video-player').requestFullscreen() - } else { - app.appMode = 'fullscreen'; - } - document.addEventListener('keydown', event => { - if (event.key === 'Escape' && app.appMode === 'fullscreen') { - this.fullscreen(false); - } - }); - } else { - ipcRenderer.send('setFullScreen', false); - app.appMode = 'player'; - } - }, - miniPlayer(flag) { - if (flag) { - this.tmpWidth = window.innerWidth; - this.tmpHeight = window.innerHeight; - ipcRenderer.send('unmaximize'); - ipcRenderer.send('windowmin', 250, 250) - ipcRenderer.send('windowresize', 300, 300, false) - app.appMode = 'mini'; - } else { - ipcRenderer.send('windowmin', 844, 410) - ipcRenderer.send('windowresize', this.tmpWidth, this.tmpHeight, false) - ipcRenderer.send('windowontop', false) - this.cfg.visual.miniplayer_top_toggle = true; - app.appMode = 'player'; - } - }, - pinMiniPlayer() { - if (this.cfg.visual.miniplayer_top_toggle) { - ipcRenderer.send('windowontop', true) - this.cfg.visual.miniplayer_top_toggle = false - } else { - ipcRenderer.send('windowontop', false) - this.cfg.visual.miniplayer_top_toggle = true; - } - }, - 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)}` - }, - toggleHideUserInfo() { - if (this.chrome.hideUserInfo) { - this.cfg.visual.showuserinfo = true - this.chrome.hideUserInfo = false - } else { - this.cfg.visual.showuserinfo = false - this.chrome.hideUserInfo = true - } - }, - isElementOverflowing(selector) { - try { - let element = document.querySelector(selector); - var overflowX = element.offsetWidth < element.scrollWidth, - overflowY = element.offsetHeight < element.scrollHeight; - element.setAttribute('data-value', '\xa0\xa0\xa0\xa0' + element.textContent); - - return (overflowX || overflowY); - } catch (e) { - return false - } - }, - async showWebRemoteQR() { - //this.webremoteqr = await ipcRenderer.invoke('setRemoteQR','') - this.webremoteurl = await ipcRenderer.invoke('showQR', '') - //this.modals.qrcode = true; - }, - checkMarquee() { - if (isElementOverflowing('#app-main > div.app-chrome > div.app-chrome--center > div > div > div.playback-info > div.song-artist') == true) { - document.getElementsByClassName('song-artist')[0].classList.add('marquee'); - document.getElementsByClassName('song-artist')[1].classList.add('marquee-after'); - } - if (isElementOverflowing('#app-main > div.app-chrome > div.app-chrome--center > div > div > div.playback-info > div.song-name') == true) { - document.getElementsByClassName('song-name')[0].classList.add('marquee'); - document.getElementsByClassName('song-name')[1].classList.add('marquee-after'); - } - }, - closeWindow() { - ipcRenderer.send('close'); - }, - darwinShare(url) { - ipcRenderer.send('share-menu', url) - }, - arrayToChunk(arr, chunkSize) { - let R = []; - for (let i = 0, len = arr.length; i < len; i += chunkSize) { - R.push(arr.slice(i, i + chunkSize)); - } - return R; - }, - SpacePause() { - const elems = document.querySelectorAll('input'); - for (elem of elems) { - if (elem === document.activeElement) { - return; - } - } - if (!this.isDev) // disable in dev mode to keep my sanity - MusicKitInterop.playPause(); - }, - async MKJSLang() { - let u = this.cfg.general.language; - // use MusicKit.getInstance or crash - try { - let item = await MusicKit.getInstance().api.v3.music(`v1/storefronts/${app.mk.storefrontId}`) - let langcodes = item.data.data[0].attributes.supportedLanguageTags; - if (langcodes) langcodes = langcodes.map(function (u) { - return u.replace(/-Han[s|t]/i, "").toLowerCase() - }) - console.log(langcodes) - let sellang = "" - if (u && langcodes.includes(u.toLowerCase().replace('_', "-"))) { - sellang = ((u.toLowerCase()).replace('_', "-")) - } else if (u && u.includes('_') && langcodes.includes(((u.toLowerCase()).replace('_', "-")).split("-")[0])) { - sellang = ((u.toLowerCase()).replace('_', "-")).split("-")[0] - } - if (sellang == "") sellang = (item.data.data[0].attributes.defaultLanguageTag).toLowerCase() - - // Fix weird locales: - if (sellang == "iw") sellang = "iw-il" - sellang = sellang.replace(/-Han[s|t]/i, "").toLowerCase() - - console.log(sellang) - return await sellang - } catch (err) { - console.log('locale err', err) - let langcodes = ['af', 'sq', 'ar', 'eu', 'bg', 'be', 'ca', 'zh', 'zh-tw', 'zh-cn', 'zh-hk', 'zh-sg', 'hr', 'cs', 'da', 'nl', 'nl-be', 'en', 'en-us', 'en-eg', 'en-au', 'en-gb', 'en-ca', 'en-nz', 'en-ie', 'en-za', 'en-jm', 'en-bz', 'en-tt', 'en-001', 'et', 'fo', 'fa', 'fi', 'fr', 'fr-ca', 'gd', 'de', 'de-ch', 'el', 'he', 'hi', 'hu', 'is', 'id', 'it', 'ja', 'ko', 'lv', 'lt', 'mk', 'mt', 'no', 'nb', 'nn', 'pl', 'pt-br', 'pt', 'rm', 'ro', 'ru', 'sr', 'sk', 'sl', 'es', 'es-mx', 'es-419', 'sv', 'th', 'ts', 'tn', 'tr', 'uk', 'ur', 've', 'vi', 'xh', 'yi', 'zu', 'ms', 'iw', 'lo', 'tl', 'kk', 'ta', 'te', 'bn', 'ga', 'ht', 'la', 'pa', 'sa']; - let sellang = "en" - if (u && langcodes.includes(u.toLowerCase().replace('_', "-"))) { - sellang = ((u.toLowerCase()).replace('_', "-")) - } else if (u && u.includes('_') && langcodes.includes(((u.toLowerCase()).replace('_', "-")).split("-")[0])) { - sellang = ((u.toLowerCase()).replace('_', "-")).split("-")[0] - } - if (sellang.startsWith("en") && this.mk.storefrontId != "us") sellang = "en-gb" - return await sellang - } - }, - skipToNextItem() { - app.prevButtonBackIndicator = false; - // app.mk.skipToNextItem() is buggy somehow so use this - if (this.mk.queue.nextPlayableItemIndex != -1 && this.mk.queue.nextPlayableItemIndex != null) - this.mk.changeToMediaAtIndex(this.mk.queue.nextPlayableItemIndex); - }, - skipToPreviousItem() { - // app.mk.skipToPreviousItem() is buggy somehow so use this - if (this.mk.queue.previousPlayableItemIndex != -1 && this.mk.queue.previousPlayableItemIndex != null) - this.mk.changeToMediaAtIndex(this.mk.queue.previousPlayableItemIndex); - }, - mediaKeyFixes() { - navigator.mediaSession.setActionHandler('previoustrack', function () { - app.prevButton() - }); - navigator.mediaSession.setActionHandler('nexttrack', function () { - app.skipToNextItem() - }); - }, - checkForUpdate() { - ipcRenderer.send('check-for-update') - ipcRenderer.on('update-response', (event, res) => { - if (res === "update-not-available") { - notyf.error(app.getLz(`settings.notyf.updateCider.${res}`)) - } else if (res === "update-downloaded") { - notyf.success(app.getLz(`settings.notyf.updateCider.${res}`)) - } else if (res === "update-error") { - notyf.error(app.getLz(`settings.notyf.updateCider.${res}`)) - } else if (res === "update-timeout") { - notyf.error(app.getLz(`settings.notyf.updateCider.${res}`)) - } - - }) - }, - } -}) - Vue.component('animated-number', { template: "
{{ displayNumber }}
", @@ -4546,27 +101,6 @@ Vue.component('sidebar-library-item', { methods: {} }); -// Key binds -document.addEventListener('keydown', function (e) { - if (e.keyCode === 70 && e.ctrlKey) { - app.$refs.searchInput.focus() - app.$refs.searchInput.select() - } -}); - -// Hang Timer -app.hangtimer = setTimeout(() => { - if (confirm("Cider is not responding. Reload the app?")) { - window.location.reload() - } -}, 10000) - -// add event listener for when window.location.hash changes -window.addEventListener("hashchange", function () { - app.appRoute(window.location.hash) -}); - - function fallbackinitMusicKit() { const request = new XMLHttpRequest(); @@ -4593,6 +127,7 @@ function fallbackinitMusicKit() { } document.addEventListener('musickitloaded', function () { + console.log('MusicKit loaded') // MusicKit global is now defined function initMusicKit() { let parsedJson = JSON.parse(this.responseText) @@ -4605,10 +140,9 @@ document.addEventListener('musickitloaded', function () { }, sourceType: 24, suppressErrorDialog: true - }); - setTimeout(() => { + }).then(() => { app.init() - }, 1000) + }) } @@ -4630,6 +164,7 @@ document.addEventListener('musickitloaded', function () { }); }); + if ('serviceWorker' in navigator) { // Use the window load event to keep the page load performant window.addEventListener('load', () => { @@ -4660,19 +195,6 @@ function uuidv4() { ); } -function refreshFocus() { - if (document.hasFocus() == false) { - app.windowFocus(false) - } else { - app.windowFocus(true) - } - setTimeout(refreshFocus, 200); -} - -app.getHTMLStyle() - -refreshFocus(); - function xmlToJson(xml) { // Create the return object @@ -4767,37 +289,3 @@ window.onerror = function (error) { console.log(error) bootbox.alert("Error occured: " + error) }; - - -// Key bind to unjam MusicKit in case it fails: CTRL+F10 -document.addEventListener('keydown', function (event) { - if (event.ctrlKey && event.keyCode == 121) { - try { - app.mk._services.mediaItemPlayback._currentPlayer.stop() - } catch (e) { - } - try { - app.mk._services.mediaItemPlayback._currentPlayer.destroy() - } catch (e) { - } - } -}); - -window.addEventListener("mouseup", (e) => { - if (e.button === 3) { - e.preventDefault() - app.navigateBack() - } else if (e.button === 4) { - e.preventDefault() - app.navigateForward() - } -}); - -document.addEventListener('keydown', function (event) { - if (event.ctrlKey && event.keyCode == 122) { - try { - ipcRenderer.send('detachDT', '') - } catch (e) { - } - } -}); diff --git a/src/renderer/js/bootbox.min.js b/src/renderer/lib/bootbox.min.js similarity index 100% rename from src/renderer/js/bootbox.min.js rename to src/renderer/lib/bootbox.min.js diff --git a/src/renderer/js/bootstrap.min.js b/src/renderer/lib/bootstrap.min.js similarity index 100% rename from src/renderer/js/bootstrap.min.js rename to src/renderer/lib/bootstrap.min.js diff --git a/src/renderer/js/fast-plural-rules.js b/src/renderer/lib/fast-plural-rules.js similarity index 100% rename from src/renderer/js/fast-plural-rules.js rename to src/renderer/lib/fast-plural-rules.js diff --git a/src/renderer/js/jquery-3.2.1.slim.min.js b/src/renderer/lib/jquery-3.2.1.slim.min.js similarity index 100% rename from src/renderer/js/jquery-3.2.1.slim.min.js rename to src/renderer/lib/jquery-3.2.1.slim.min.js diff --git a/src/renderer/js/less.js b/src/renderer/lib/less.js similarity index 100% rename from src/renderer/js/less.js rename to src/renderer/lib/less.js diff --git a/src/renderer/js/notyf.min.js b/src/renderer/lib/notyf.min.js similarity index 100% rename from src/renderer/js/notyf.min.js rename to src/renderer/lib/notyf.min.js diff --git a/src/renderer/js/popper.min.js b/src/renderer/lib/popper.min.js similarity index 100% rename from src/renderer/js/popper.min.js rename to src/renderer/lib/popper.min.js diff --git a/src/renderer/lib/resonance-audio.min.js b/src/renderer/lib/resonance-audio.min.js new file mode 100644 index 00000000..7d4349e1 --- /dev/null +++ b/src/renderer/lib/resonance-audio.min.js @@ -0,0 +1,181 @@ +!function(A,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e=t();for(var i in e)("object"==typeof exports?exports:A)[i]=e[i]}}(this,function(){return function(A){function t(i){if(e[i])return e[i].exports;var n=e[i]={i:i,l:!1,exports:{}};return A[i].call(n.exports,n,n.exports,t),n.l=!0,n.exports}var e={};return t.m=A,t.c=e,t.d=function(A,e,i){t.o(A,e)||Object.defineProperty(A,e,{configurable:!1,enumerable:!0,get:i})},t.n=function(A){var e=A&&A.__esModule?function(){return A.default}:function(){return A};return t.d(e,"a",e),e},t.o=function(A,t){return Object.prototype.hasOwnProperty.call(A,t)},t.p="",t(t.s=10)}([function(A,t,e){"use strict";/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +function i(){}i.DEFAULT_SOURCE_GAIN=1,i.LISTENER_MAX_OUTSIDE_ROOM_DISTANCE=1,i.SOURCE_MAX_OUTSIDE_ROOM_DISTANCE=1,i.DEFAULT_SOURCE_DISTANCE=1,i.DEFAULT_POSITION=[0,0,0],i.DEFAULT_FORWARD=[0,0,-1],i.DEFAULT_UP=[0,1,0],i.DEFAULT_RIGHT=[1,0,0],i.DEFAULT_SPEED_OF_SOUND=343,i.ATTENUATION_ROLLOFFS=["logarithmic","linear","none"],i.DEFAULT_ATTENUATION_ROLLOFF="logarithmic",i.DEFAULT_MIN_DISTANCE=1,i.DEFAULT_MAX_DISTANCE=1e3,i.DEFAULT_DIRECTIVITY_ALPHA=0,i.DEFAULT_DIRECTIVITY_SHARPNESS=1,i.DEFAULT_AZIMUTH=0,i.DEFAULT_ELEVATION=0,i.DEFAULT_AMBISONIC_ORDER=1,i.DEFAULT_SOURCE_WIDTH=0,i.DEFAULT_REFLECTION_MAX_DURATION=.5,i.DEFAULT_REFLECTION_CUTOFF_FREQUENCY=6400,i.DEFAULT_REFLECTION_COEFFICIENTS={left:0,right:0,front:0,back:0,down:0,up:0},i.DEFAULT_REFLECTION_MIN_DISTANCE=1,i.DEFAULT_ROOM_DIMENSIONS={width:0,height:0,depth:0},i.DEFAULT_REFLECTION_MULTIPLIER=1,i.DEFAULT_REVERB_BANDWIDTH=1,i.DEFAULT_REVERB_DURATION_MULTIPLIER=1,i.DEFAULT_REVERB_PREDELAY=1.5,i.DEFAULT_REVERB_TAIL_ONSET=3.8,i.DEFAULT_REVERB_GAIN=.01,i.DEFAULT_REVERB_MAX_DURATION=3,i.NUMBER_REVERB_FREQUENCY_BANDS=(i.DEFAULT_REVERB_FREQUENCY_BANDS=[31.25,62.5,125,250,500,1e3,2e3,4e3,8e3]).length,i.DEFAULT_REVERB_DURATIONS=new Float32Array(i.NUMBER_REVERB_FREQUENCY_BANDS),i.ROOM_MATERIAL_COEFFICIENTS={transparent:[1,1,1,1,1,1,1,1,1],"acoustic-ceiling-tiles":[.672,.675,.7,.66,.72,.92,.88,.75,1],"brick-bare":[.03,.03,.03,.03,.03,.04,.05,.07,.14],"brick-painted":[.006,.007,.01,.01,.02,.02,.02,.03,.06],"concrete-block-coarse":[.36,.36,.36,.44,.31,.29,.39,.25,.5],"concrete-block-painted":[.092,.09,.1,.05,.06,.07,.09,.08,.16],"curtain-heavy":[.073,.106,.14,.35,.55,.72,.7,.65,1],"fiber-glass-insulation":[.193,.22,.22,.82,.99,.99,.99,.99,1],"glass-thin":[.18,.169,.18,.06,.04,.03,.02,.02,.04],"glass-thick":[.35,.35,.35,.25,.18,.12,.07,.04,.08],grass:[.05,.05,.15,.25,.4,.55,.6,.6,.6],"linoleum-on-concrete":[.02,.02,.02,.03,.03,.03,.03,.02,.04],marble:[.01,.01,.01,.01,.01,.01,.02,.02,.04],metal:[.03,.035,.04,.04,.05,.05,.05,.07,.09],"parquet-on-concrete":[.028,.03,.04,.04,.07,.06,.06,.07,.14],"plaster-rough":[.017,.018,.02,.03,.04,.05,.04,.03,.06],"plaster-smooth":[.011,.012,.013,.015,.02,.03,.04,.05,.1],"plywood-panel":[.4,.34,.28,.22,.17,.09,.1,.11,.22],"polished-concrete-or-tile":[.008,.008,.01,.01,.015,.02,.02,.02,.04],"sheet-rock":[.29,.279,.29,.1,.05,.04,.07,.09,.18],"water-or-ice-surface":[.006,.006,.008,.008,.013,.015,.02,.025,.05],"wood-ceiling":[.15,.147,.15,.11,.1,.07,.06,.07,.14],"wood-panel":[.28,.28,.28,.22,.17,.09,.1,.11,.22],uniform:[.5,.5,.5,.5,.5,.5,.5,.5,.5]},i.DEFAULT_ROOM_MATERIALS={left:"transparent",right:"transparent",front:"transparent",back:"transparent",down:"transparent",up:"transparent"},i.NUMBER_REFLECTION_AVERAGING_BANDS=3,i.ROOM_STARTING_AVERAGING_BAND=4,i.ROOM_MIN_VOLUME=1e-4,i.ROOM_AIR_ABSORPTION_COEFFICIENTS=[6e-4,6e-4,7e-4,8e-4,.001,.0015,.0026,.006,.0207],i.ROOM_EYRING_CORRECTION_COEFFICIENT=1.38,i.TWO_PI=6.28318530717959,i.TWENTY_FOUR_LOG10=55.2620422318571,i.LOG1000=6.90775527898214,i.LOG2_DIV2=.346573590279973,i.DEGREES_TO_RADIANS=.017453292519943,i.RADIANS_TO_DEGREES=57.29577951308232,i.EPSILON_FLOAT=1e-8,i.log=function(){window.console.log.apply(window.console,["%c[ResonanceAudio]%c "+Array.prototype.slice.call(arguments).join(" ")+" %c(@"+performance.now().toFixed(2)+"ms)","background: #BBDEFB; color: #FF5722; font-weight: 700","font-weight: 400","color: #AAA"])},i.normalizeVector=function(A){let e=Math.sqrt(A[0]*A[0]+A[1]*A[1]+A[2]*A[2]);return e>t.EPSILON_FLOAT&&(e=1/e,A[0]*=e,A[1]*=e,A[2]*=e),A},i.crossProduct=function(A,t){return[A[1]*t[2]-A[2]*t[1],A[2]*t[0]-A[0]*t[2],A[0]*t[1]-A[1]*t[0]]},A.exports=i},function(A,t,e){"use strict";function i(A,t){void 0==t&&(t={}),void 0==t.ambisonicOrder&&(t.ambisonicOrder=s.DEFAULT_AMBISONIC_ORDER),void 0==t.azimuth&&(t.azimuth=s.DEFAULT_AZIMUTH),void 0==t.elevation&&(t.elevation=s.DEFAULT_ELEVATION),void 0==t.sourceWidth&&(t.sourceWidth=s.DEFAULT_SOURCE_WIDTH),this._context=A,this.input=A.createGain(),this._channelGain=[],this._merger=void 0,this.output=A.createGain(),this.setAmbisonicOrder(t.ambisonicOrder),this._azimuth=t.azimuth,this._elevation=t.elevation,this.setSourceWidth(t.sourceWidth)}/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const n=e(3),s=e(0);i.prototype.setAmbisonicOrder=function(A){this._ambisonicOrder=i.validateAmbisonicOrder(A),this.input.disconnect();for(let e=0;en.SPHERICAL_HARMONICS_MAX_ORDER&&(s.log("Error: Unable to render ambisonic order",options.ambisonicOrder,"(Max order is",n.SPHERICAL_HARMONICS_MAX_ORDER,")\nUsing max order instead."),options.ambisonicOrder=n.SPHERICAL_HARMONICS_MAX_ORDER),A},A.exports=i},function(A,t,e){"use strict";function i(A,t){void 0==t&&(t={}),void 0==t.ambisonicOrder&&(t.ambisonicOrder=o.DEFAULT_AMBISONIC_ORDER),void 0==t.position&&(t.position=o.DEFAULT_POSITION.slice()),void 0==t.forward&&(t.forward=o.DEFAULT_FORWARD.slice()),void 0==t.up&&(t.up=o.DEFAULT_UP.slice()),this.position=new Float32Array(3),this._tempMatrix3=new Float32Array(9),this._ambisonicOrder=s.validateAmbisonicOrder(t.ambisonicOrder),this._context=A,1==this._ambisonicOrder?this._renderer=n.Omnitone.createFOARenderer(A,{}):this._ambisonicOrder>1&&(this._renderer=n.Omnitone.createHOARenderer(A,{ambisonicOrder:this._ambisonicOrder})),this.input=A.createGain(),this.output=A.createGain(),this.ambisonicOutput=A.createGain();let e=this;this._renderer.initialize().then(function(){e.input.connect(e._renderer.input),e._ambisonicOrder>1?e._renderer._hoaRotator.output.connect(e.ambisonicOutput):e._renderer._foaRotator.output.connect(e.ambisonicOutput),e._renderer.output.connect(e.output)}),this.setOrientation(t.forward[0],t.forward[1],t.forward[2],t.up[0],t.up[1],t.up[2])}/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const n=e(12),s=e(1),o=e(0);i.prototype.setOrientation=function(A,t,e,i,n,s){let r=o.crossProduct([A,t,e],[i,n,s]);this._tempMatrix3[0]=r[0],this._tempMatrix3[1]=r[1],this._tempMatrix3[2]=r[2],this._tempMatrix3[3]=i,this._tempMatrix3[4]=n,this._tempMatrix3[5]=s,this._tempMatrix3[6]=A,this._tempMatrix3[7]=t,this._tempMatrix3[8]=e,this._renderer.setRotationMatrix3(this._tempMatrix3)},i.prototype.setFromMatrix=function(A){this._renderer.setRotationMatrix4(A.elements),this.position[0]=A.elements[12],this.position[1]=A.elements[13],this.position[2]=A.elements[14]},A.exports=i},function(A,t,e){"use strict";t.SPHERICAL_HARMONICS=[[[0,0,0,1,1,1],[.052336,.034899,.017452,.999848,.999391,.99863],[.104528,.069756,.034899,.999391,.997564,.994522],[.156434,.104528,.052336,.99863,.994522,.987688],[.207912,.139173,.069756,.997564,.990268,.978148],[.258819,.173648,.087156,.996195,.984808,.965926],[.309017,.207912,.104528,.994522,.978148,.951057],[.358368,.241922,.121869,.992546,.970296,.93358],[.406737,.275637,.139173,.990268,.961262,.913545],[.45399,.309017,.156434,.987688,.951057,.891007],[.5,.34202,.173648,.984808,.939693,.866025],[.544639,.374607,.190809,.981627,.927184,.838671],[.587785,.406737,.207912,.978148,.913545,.809017],[.62932,.438371,.224951,.97437,.898794,.777146],[.669131,.469472,.241922,.970296,.882948,.743145],[.707107,.5,.258819,.965926,.866025,.707107],[.743145,.529919,.275637,.961262,.848048,.669131],[.777146,.559193,.292372,.956305,.829038,.62932],[.809017,.587785,.309017,.951057,.809017,.587785],[.838671,.615661,.325568,.945519,.788011,.544639],[.866025,.642788,.34202,.939693,.766044,.5],[.891007,.669131,.358368,.93358,.743145,.45399],[.913545,.694658,.374607,.927184,.71934,.406737],[.93358,.71934,.390731,.920505,.694658,.358368],[.951057,.743145,.406737,.913545,.669131,.309017],[.965926,.766044,.422618,.906308,.642788,.258819],[.978148,.788011,.438371,.898794,.615661,.207912],[.987688,.809017,.45399,.891007,.587785,.156434],[.994522,.829038,.469472,.882948,.559193,.104528],[.99863,.848048,.48481,.87462,.529919,.052336],[1,.866025,.5,.866025,.5,0],[.99863,.882948,.515038,.857167,.469472,-.052336],[.994522,.898794,.529919,.848048,.438371,-.104528],[.987688,.913545,.544639,.838671,.406737,-.156434],[.978148,.927184,.559193,.829038,.374607,-.207912],[.965926,.939693,.573576,.819152,.34202,-.258819],[.951057,.951057,.587785,.809017,.309017,-.309017],[.93358,.961262,.601815,.798636,.275637,-.358368],[.913545,.970296,.615661,.788011,.241922,-.406737],[.891007,.978148,.62932,.777146,.207912,-.45399],[.866025,.984808,.642788,.766044,.173648,-.5],[.838671,.990268,.656059,.75471,.139173,-.544639],[.809017,.994522,.669131,.743145,.104528,-.587785],[.777146,.997564,.681998,.731354,.069756,-.62932],[.743145,.999391,.694658,.71934,.034899,-.669131],[.707107,1,.707107,.707107,0,-.707107],[.669131,.999391,.71934,.694658,-.034899,-.743145],[.62932,.997564,.731354,.681998,-.069756,-.777146],[.587785,.994522,.743145,.669131,-.104528,-.809017],[.544639,.990268,.75471,.656059,-.139173,-.838671],[.5,.984808,.766044,.642788,-.173648,-.866025],[.45399,.978148,.777146,.62932,-.207912,-.891007],[.406737,.970296,.788011,.615661,-.241922,-.913545],[.358368,.961262,.798636,.601815,-.275637,-.93358],[.309017,.951057,.809017,.587785,-.309017,-.951057],[.258819,.939693,.819152,.573576,-.34202,-.965926],[.207912,.927184,.829038,.559193,-.374607,-.978148],[.156434,.913545,.838671,.544639,-.406737,-.987688],[.104528,.898794,.848048,.529919,-.438371,-.994522],[.052336,.882948,.857167,.515038,-.469472,-.99863],[0,.866025,.866025,.5,-.5,-1],[-.052336,.848048,.87462,.48481,-.529919,-.99863],[-.104528,.829038,.882948,.469472,-.559193,-.994522],[-.156434,.809017,.891007,.45399,-.587785,-.987688],[-.207912,.788011,.898794,.438371,-.615661,-.978148],[-.258819,.766044,.906308,.422618,-.642788,-.965926],[-.309017,.743145,.913545,.406737,-.669131,-.951057],[-.358368,.71934,.920505,.390731,-.694658,-.93358],[-.406737,.694658,.927184,.374607,-.71934,-.913545],[-.45399,.669131,.93358,.358368,-.743145,-.891007],[-.5,.642788,.939693,.34202,-.766044,-.866025],[-.544639,.615661,.945519,.325568,-.788011,-.838671],[-.587785,.587785,.951057,.309017,-.809017,-.809017],[-.62932,.559193,.956305,.292372,-.829038,-.777146],[-.669131,.529919,.961262,.275637,-.848048,-.743145],[-.707107,.5,.965926,.258819,-.866025,-.707107],[-.743145,.469472,.970296,.241922,-.882948,-.669131],[-.777146,.438371,.97437,.224951,-.898794,-.62932],[-.809017,.406737,.978148,.207912,-.913545,-.587785],[-.838671,.374607,.981627,.190809,-.927184,-.544639],[-.866025,.34202,.984808,.173648,-.939693,-.5],[-.891007,.309017,.987688,.156434,-.951057,-.45399],[-.913545,.275637,.990268,.139173,-.961262,-.406737],[-.93358,.241922,.992546,.121869,-.970296,-.358368],[-.951057,.207912,.994522,.104528,-.978148,-.309017],[-.965926,.173648,.996195,.087156,-.984808,-.258819],[-.978148,.139173,.997564,.069756,-.990268,-.207912],[-.987688,.104528,.99863,.052336,-.994522,-.156434],[-.994522,.069756,.999391,.034899,-.997564,-.104528],[-.99863,.034899,.999848,.017452,-.999391,-.052336],[-1,0,1,0,-1,-0],[-.99863,-.034899,.999848,-.017452,-.999391,.052336],[-.994522,-.069756,.999391,-.034899,-.997564,.104528],[-.987688,-.104528,.99863,-.052336,-.994522,.156434],[-.978148,-.139173,.997564,-.069756,-.990268,.207912],[-.965926,-.173648,.996195,-.087156,-.984808,.258819],[-.951057,-.207912,.994522,-.104528,-.978148,.309017],[-.93358,-.241922,.992546,-.121869,-.970296,.358368],[-.913545,-.275637,.990268,-.139173,-.961262,.406737],[-.891007,-.309017,.987688,-.156434,-.951057,.45399],[-.866025,-.34202,.984808,-.173648,-.939693,.5],[-.838671,-.374607,.981627,-.190809,-.927184,.544639],[-.809017,-.406737,.978148,-.207912,-.913545,.587785],[-.777146,-.438371,.97437,-.224951,-.898794,.62932],[-.743145,-.469472,.970296,-.241922,-.882948,.669131],[-.707107,-.5,.965926,-.258819,-.866025,.707107],[-.669131,-.529919,.961262,-.275637,-.848048,.743145],[-.62932,-.559193,.956305,-.292372,-.829038,.777146],[-.587785,-.587785,.951057,-.309017,-.809017,.809017],[-.544639,-.615661,.945519,-.325568,-.788011,.838671],[-.5,-.642788,.939693,-.34202,-.766044,.866025],[-.45399,-.669131,.93358,-.358368,-.743145,.891007],[-.406737,-.694658,.927184,-.374607,-.71934,.913545],[-.358368,-.71934,.920505,-.390731,-.694658,.93358],[-.309017,-.743145,.913545,-.406737,-.669131,.951057],[-.258819,-.766044,.906308,-.422618,-.642788,.965926],[-.207912,-.788011,.898794,-.438371,-.615661,.978148],[-.156434,-.809017,.891007,-.45399,-.587785,.987688],[-.104528,-.829038,.882948,-.469472,-.559193,.994522],[-.052336,-.848048,.87462,-.48481,-.529919,.99863],[-0,-.866025,.866025,-.5,-.5,1],[.052336,-.882948,.857167,-.515038,-.469472,.99863],[.104528,-.898794,.848048,-.529919,-.438371,.994522],[.156434,-.913545,.838671,-.544639,-.406737,.987688],[.207912,-.927184,.829038,-.559193,-.374607,.978148],[.258819,-.939693,.819152,-.573576,-.34202,.965926],[.309017,-.951057,.809017,-.587785,-.309017,.951057],[.358368,-.961262,.798636,-.601815,-.275637,.93358],[.406737,-.970296,.788011,-.615661,-.241922,.913545],[.45399,-.978148,.777146,-.62932,-.207912,.891007],[.5,-.984808,.766044,-.642788,-.173648,.866025],[.544639,-.990268,.75471,-.656059,-.139173,.838671],[.587785,-.994522,.743145,-.669131,-.104528,.809017],[.62932,-.997564,.731354,-.681998,-.069756,.777146],[.669131,-.999391,.71934,-.694658,-.034899,.743145],[.707107,-1,.707107,-.707107,-0,.707107],[.743145,-.999391,.694658,-.71934,.034899,.669131],[.777146,-.997564,.681998,-.731354,.069756,.62932],[.809017,-.994522,.669131,-.743145,.104528,.587785],[.838671,-.990268,.656059,-.75471,.139173,.544639],[.866025,-.984808,.642788,-.766044,.173648,.5],[.891007,-.978148,.62932,-.777146,.207912,.45399],[.913545,-.970296,.615661,-.788011,.241922,.406737],[.93358,-.961262,.601815,-.798636,.275637,.358368],[.951057,-.951057,.587785,-.809017,.309017,.309017],[.965926,-.939693,.573576,-.819152,.34202,.258819],[.978148,-.927184,.559193,-.829038,.374607,.207912],[.987688,-.913545,.544639,-.838671,.406737,.156434],[.994522,-.898794,.529919,-.848048,.438371,.104528],[.99863,-.882948,.515038,-.857167,.469472,.052336],[1,-.866025,.5,-.866025,.5,0],[.99863,-.848048,.48481,-.87462,.529919,-.052336],[.994522,-.829038,.469472,-.882948,.559193,-.104528],[.987688,-.809017,.45399,-.891007,.587785,-.156434],[.978148,-.788011,.438371,-.898794,.615661,-.207912],[.965926,-.766044,.422618,-.906308,.642788,-.258819],[.951057,-.743145,.406737,-.913545,.669131,-.309017],[.93358,-.71934,.390731,-.920505,.694658,-.358368],[.913545,-.694658,.374607,-.927184,.71934,-.406737],[.891007,-.669131,.358368,-.93358,.743145,-.45399],[.866025,-.642788,.34202,-.939693,.766044,-.5],[.838671,-.615661,.325568,-.945519,.788011,-.544639],[.809017,-.587785,.309017,-.951057,.809017,-.587785],[.777146,-.559193,.292372,-.956305,.829038,-.62932],[.743145,-.529919,.275637,-.961262,.848048,-.669131],[.707107,-.5,.258819,-.965926,.866025,-.707107],[.669131,-.469472,.241922,-.970296,.882948,-.743145],[.62932,-.438371,.224951,-.97437,.898794,-.777146],[.587785,-.406737,.207912,-.978148,.913545,-.809017],[.544639,-.374607,.190809,-.981627,.927184,-.838671],[.5,-.34202,.173648,-.984808,.939693,-.866025],[.45399,-.309017,.156434,-.987688,.951057,-.891007],[.406737,-.275637,.139173,-.990268,.961262,-.913545],[.358368,-.241922,.121869,-.992546,.970296,-.93358],[.309017,-.207912,.104528,-.994522,.978148,-.951057],[.258819,-.173648,.087156,-.996195,.984808,-.965926],[.207912,-.139173,.069756,-.997564,.990268,-.978148],[.156434,-.104528,.052336,-.99863,.994522,-.987688],[.104528,-.069756,.034899,-.999391,.997564,-.994522],[.052336,-.034899,.017452,-.999848,.999391,-.99863],[0,-0,0,-1,1,-1],[-.052336,.034899,-.017452,-.999848,.999391,-.99863],[-.104528,.069756,-.034899,-.999391,.997564,-.994522],[-.156434,.104528,-.052336,-.99863,.994522,-.987688],[-.207912,.139173,-.069756,-.997564,.990268,-.978148],[-.258819,.173648,-.087156,-.996195,.984808,-.965926],[-.309017,.207912,-.104528,-.994522,.978148,-.951057],[-.358368,.241922,-.121869,-.992546,.970296,-.93358],[-.406737,.275637,-.139173,-.990268,.961262,-.913545],[-.45399,.309017,-.156434,-.987688,.951057,-.891007],[-.5,.34202,-.173648,-.984808,.939693,-.866025],[-.544639,.374607,-.190809,-.981627,.927184,-.838671],[-.587785,.406737,-.207912,-.978148,.913545,-.809017],[-.62932,.438371,-.224951,-.97437,.898794,-.777146],[-.669131,.469472,-.241922,-.970296,.882948,-.743145],[-.707107,.5,-.258819,-.965926,.866025,-.707107],[-.743145,.529919,-.275637,-.961262,.848048,-.669131],[-.777146,.559193,-.292372,-.956305,.829038,-.62932],[-.809017,.587785,-.309017,-.951057,.809017,-.587785],[-.838671,.615661,-.325568,-.945519,.788011,-.544639],[-.866025,.642788,-.34202,-.939693,.766044,-.5],[-.891007,.669131,-.358368,-.93358,.743145,-.45399],[-.913545,.694658,-.374607,-.927184,.71934,-.406737],[-.93358,.71934,-.390731,-.920505,.694658,-.358368],[-.951057,.743145,-.406737,-.913545,.669131,-.309017],[-.965926,.766044,-.422618,-.906308,.642788,-.258819],[-.978148,.788011,-.438371,-.898794,.615661,-.207912],[-.987688,.809017,-.45399,-.891007,.587785,-.156434],[-.994522,.829038,-.469472,-.882948,.559193,-.104528],[-.99863,.848048,-.48481,-.87462,.529919,-.052336],[-1,.866025,-.5,-.866025,.5,0],[-.99863,.882948,-.515038,-.857167,.469472,.052336],[-.994522,.898794,-.529919,-.848048,.438371,.104528],[-.987688,.913545,-.544639,-.838671,.406737,.156434],[-.978148,.927184,-.559193,-.829038,.374607,.207912],[-.965926,.939693,-.573576,-.819152,.34202,.258819],[-.951057,.951057,-.587785,-.809017,.309017,.309017],[-.93358,.961262,-.601815,-.798636,.275637,.358368],[-.913545,.970296,-.615661,-.788011,.241922,.406737],[-.891007,.978148,-.62932,-.777146,.207912,.45399],[-.866025,.984808,-.642788,-.766044,.173648,.5],[-.838671,.990268,-.656059,-.75471,.139173,.544639],[-.809017,.994522,-.669131,-.743145,.104528,.587785],[-.777146,.997564,-.681998,-.731354,.069756,.62932],[-.743145,.999391,-.694658,-.71934,.034899,.669131],[-.707107,1,-.707107,-.707107,0,.707107],[-.669131,.999391,-.71934,-.694658,-.034899,.743145],[-.62932,.997564,-.731354,-.681998,-.069756,.777146],[-.587785,.994522,-.743145,-.669131,-.104528,.809017],[-.544639,.990268,-.75471,-.656059,-.139173,.838671],[-.5,.984808,-.766044,-.642788,-.173648,.866025],[-.45399,.978148,-.777146,-.62932,-.207912,.891007],[-.406737,.970296,-.788011,-.615661,-.241922,.913545],[-.358368,.961262,-.798636,-.601815,-.275637,.93358],[-.309017,.951057,-.809017,-.587785,-.309017,.951057],[-.258819,.939693,-.819152,-.573576,-.34202,.965926],[-.207912,.927184,-.829038,-.559193,-.374607,.978148],[-.156434,.913545,-.838671,-.544639,-.406737,.987688],[-.104528,.898794,-.848048,-.529919,-.438371,.994522],[-.052336,.882948,-.857167,-.515038,-.469472,.99863],[-0,.866025,-.866025,-.5,-.5,1],[.052336,.848048,-.87462,-.48481,-.529919,.99863],[.104528,.829038,-.882948,-.469472,-.559193,.994522],[.156434,.809017,-.891007,-.45399,-.587785,.987688],[.207912,.788011,-.898794,-.438371,-.615661,.978148],[.258819,.766044,-.906308,-.422618,-.642788,.965926],[.309017,.743145,-.913545,-.406737,-.669131,.951057],[.358368,.71934,-.920505,-.390731,-.694658,.93358],[.406737,.694658,-.927184,-.374607,-.71934,.913545],[.45399,.669131,-.93358,-.358368,-.743145,.891007],[.5,.642788,-.939693,-.34202,-.766044,.866025],[.544639,.615661,-.945519,-.325568,-.788011,.838671],[.587785,.587785,-.951057,-.309017,-.809017,.809017],[.62932,.559193,-.956305,-.292372,-.829038,.777146],[.669131,.529919,-.961262,-.275637,-.848048,.743145],[.707107,.5,-.965926,-.258819,-.866025,.707107],[.743145,.469472,-.970296,-.241922,-.882948,.669131],[.777146,.438371,-.97437,-.224951,-.898794,.62932],[.809017,.406737,-.978148,-.207912,-.913545,.587785],[.838671,.374607,-.981627,-.190809,-.927184,.544639],[.866025,.34202,-.984808,-.173648,-.939693,.5],[.891007,.309017,-.987688,-.156434,-.951057,.45399],[.913545,.275637,-.990268,-.139173,-.961262,.406737],[.93358,.241922,-.992546,-.121869,-.970296,.358368],[.951057,.207912,-.994522,-.104528,-.978148,.309017],[.965926,.173648,-.996195,-.087156,-.984808,.258819],[.978148,.139173,-.997564,-.069756,-.990268,.207912],[.987688,.104528,-.99863,-.052336,-.994522,.156434],[.994522,.069756,-.999391,-.034899,-.997564,.104528],[.99863,.034899,-.999848,-.017452,-.999391,.052336],[1,0,-1,-0,-1,0],[.99863,-.034899,-.999848,.017452,-.999391,-.052336],[.994522,-.069756,-.999391,.034899,-.997564,-.104528],[.987688,-.104528,-.99863,.052336,-.994522,-.156434],[.978148,-.139173,-.997564,.069756,-.990268,-.207912],[.965926,-.173648,-.996195,.087156,-.984808,-.258819],[.951057,-.207912,-.994522,.104528,-.978148,-.309017],[.93358,-.241922,-.992546,.121869,-.970296,-.358368],[.913545,-.275637,-.990268,.139173,-.961262,-.406737],[.891007,-.309017,-.987688,.156434,-.951057,-.45399],[.866025,-.34202,-.984808,.173648,-.939693,-.5],[.838671,-.374607,-.981627,.190809,-.927184,-.544639],[.809017,-.406737,-.978148,.207912,-.913545,-.587785],[.777146,-.438371,-.97437,.224951,-.898794,-.62932],[.743145,-.469472,-.970296,.241922,-.882948,-.669131],[.707107,-.5,-.965926,.258819,-.866025,-.707107],[.669131,-.529919,-.961262,.275637,-.848048,-.743145],[.62932,-.559193,-.956305,.292372,-.829038,-.777146],[.587785,-.587785,-.951057,.309017,-.809017,-.809017],[.544639,-.615661,-.945519,.325568,-.788011,-.838671],[.5,-.642788,-.939693,.34202,-.766044,-.866025],[.45399,-.669131,-.93358,.358368,-.743145,-.891007],[.406737,-.694658,-.927184,.374607,-.71934,-.913545],[.358368,-.71934,-.920505,.390731,-.694658,-.93358],[.309017,-.743145,-.913545,.406737,-.669131,-.951057],[.258819,-.766044,-.906308,.422618,-.642788,-.965926],[.207912,-.788011,-.898794,.438371,-.615661,-.978148],[.156434,-.809017,-.891007,.45399,-.587785,-.987688],[.104528,-.829038,-.882948,.469472,-.559193,-.994522],[.052336,-.848048,-.87462,.48481,-.529919,-.99863],[0,-.866025,-.866025,.5,-.5,-1],[-.052336,-.882948,-.857167,.515038,-.469472,-.99863],[-.104528,-.898794,-.848048,.529919,-.438371,-.994522],[-.156434,-.913545,-.838671,.544639,-.406737,-.987688],[-.207912,-.927184,-.829038,.559193,-.374607,-.978148],[-.258819,-.939693,-.819152,.573576,-.34202,-.965926],[-.309017,-.951057,-.809017,.587785,-.309017,-.951057],[-.358368,-.961262,-.798636,.601815,-.275637,-.93358],[-.406737,-.970296,-.788011,.615661,-.241922,-.913545],[-.45399,-.978148,-.777146,.62932,-.207912,-.891007],[-.5,-.984808,-.766044,.642788,-.173648,-.866025],[-.544639,-.990268,-.75471,.656059,-.139173,-.838671],[-.587785,-.994522,-.743145,.669131,-.104528,-.809017],[-.62932,-.997564,-.731354,.681998,-.069756,-.777146],[-.669131,-.999391,-.71934,.694658,-.034899,-.743145],[-.707107,-1,-.707107,.707107,-0,-.707107],[-.743145,-.999391,-.694658,.71934,.034899,-.669131],[-.777146,-.997564,-.681998,.731354,.069756,-.62932],[-.809017,-.994522,-.669131,.743145,.104528,-.587785],[-.838671,-.990268,-.656059,.75471,.139173,-.544639],[-.866025,-.984808,-.642788,.766044,.173648,-.5],[-.891007,-.978148,-.62932,.777146,.207912,-.45399],[-.913545,-.970296,-.615661,.788011,.241922,-.406737],[-.93358,-.961262,-.601815,.798636,.275637,-.358368],[-.951057,-.951057,-.587785,.809017,.309017,-.309017],[-.965926,-.939693,-.573576,.819152,.34202,-.258819],[-.978148,-.927184,-.559193,.829038,.374607,-.207912],[-.987688,-.913545,-.544639,.838671,.406737,-.156434],[-.994522,-.898794,-.529919,.848048,.438371,-.104528],[-.99863,-.882948,-.515038,.857167,.469472,-.052336],[-1,-.866025,-.5,.866025,.5,-0],[-.99863,-.848048,-.48481,.87462,.529919,.052336],[-.994522,-.829038,-.469472,.882948,.559193,.104528],[-.987688,-.809017,-.45399,.891007,.587785,.156434],[-.978148,-.788011,-.438371,.898794,.615661,.207912],[-.965926,-.766044,-.422618,.906308,.642788,.258819],[-.951057,-.743145,-.406737,.913545,.669131,.309017],[-.93358,-.71934,-.390731,.920505,.694658,.358368],[-.913545,-.694658,-.374607,.927184,.71934,.406737],[-.891007,-.669131,-.358368,.93358,.743145,.45399],[-.866025,-.642788,-.34202,.939693,.766044,.5],[-.838671,-.615661,-.325568,.945519,.788011,.544639],[-.809017,-.587785,-.309017,.951057,.809017,.587785],[-.777146,-.559193,-.292372,.956305,.829038,.62932],[-.743145,-.529919,-.275637,.961262,.848048,.669131],[-.707107,-.5,-.258819,.965926,.866025,.707107],[-.669131,-.469472,-.241922,.970296,.882948,.743145],[-.62932,-.438371,-.224951,.97437,.898794,.777146],[-.587785,-.406737,-.207912,.978148,.913545,.809017],[-.544639,-.374607,-.190809,.981627,.927184,.838671],[-.5,-.34202,-.173648,.984808,.939693,.866025],[-.45399,-.309017,-.156434,.987688,.951057,.891007],[-.406737,-.275637,-.139173,.990268,.961262,.913545],[-.358368,-.241922,-.121869,.992546,.970296,.93358],[-.309017,-.207912,-.104528,.994522,.978148,.951057],[-.258819,-.173648,-.087156,.996195,.984808,.965926],[-.207912,-.139173,-.069756,.997564,.990268,.978148],[-.156434,-.104528,-.052336,.99863,.994522,.987688],[-.104528,-.069756,-.034899,.999391,.997564,.994522],[-.052336,-.034899,-.017452,.999848,.999391,.99863]],[[-1,-0,1,-0,0,-1,-0,0,-0],[-.999848,.017452,.999543,-.030224,264e-6,-.999086,.042733,-59e-5,4e-6],[-.999391,.034899,.998173,-.060411,.001055,-.996348,.085356,-.002357,34e-6],[-.99863,.052336,.995891,-.090524,.002372,-.991791,.127757,-.005297,113e-6],[-.997564,.069756,.992701,-.120527,.004214,-.985429,.169828,-.0094,268e-6],[-.996195,.087156,.988606,-.150384,.006578,-.977277,.21146,-.014654,523e-6],[-.994522,.104528,.983611,-.180057,.009462,-.967356,.252544,-.021043,903e-6],[-.992546,.121869,.977722,-.209511,.012862,-.955693,.292976,-.028547,.001431],[-.990268,.139173,.970946,-.238709,.016774,-.942316,.332649,-.037143,.002131],[-.987688,.156434,.963292,-.267617,.021193,-.927262,.371463,-.046806,.003026],[-.984808,.173648,.954769,-.296198,.026114,-.910569,.409317,-.057505,.00414],[-.981627,.190809,.945388,-.324419,.03153,-.892279,.446114,-.069209,.005492],[-.978148,.207912,.935159,-.352244,.037436,-.872441,.481759,-.08188,.007105],[-.97437,.224951,.924096,-.379641,.043823,-.851105,.516162,-.095481,.008999],[-.970296,.241922,.912211,-.406574,.050685,-.828326,.549233,-.109969,.011193],[-.965926,.258819,.899519,-.433013,.058013,-.804164,.580889,-.1253,.013707],[-.961262,.275637,.886036,-.458924,.065797,-.77868,.61105,-.141427,.016556],[-.956305,.292372,.871778,-.484275,.074029,-.75194,.639639,-.158301,.019758],[-.951057,.309017,.856763,-.509037,.082698,-.724012,.666583,-.175868,.023329],[-.945519,.325568,.841008,-.533178,.091794,-.694969,.691816,-.194075,.027281],[-.939693,.34202,.824533,-.55667,.101306,-.664885,.715274,-.212865,.03163],[-.93358,.358368,.807359,-.579484,.111222,-.633837,.736898,-.23218,.036385],[-.927184,.374607,.789505,-.601592,.121529,-.601904,.756637,-.25196,.041559],[-.920505,.390731,.770994,-.622967,.132217,-.569169,.774442,-.272143,.04716],[-.913545,.406737,.751848,-.643582,.143271,-.535715,.79027,-.292666,.053196],[-.906308,.422618,.732091,-.663414,.154678,-.501627,.804083,-.313464,.059674],[-.898794,.438371,.711746,-.682437,.166423,-.466993,.81585,-.334472,.066599],[-.891007,.45399,.690839,-.700629,.178494,-.431899,.825544,-.355623,.073974],[-.882948,.469472,.669395,-.717968,.190875,-.396436,.833145,-.376851,.081803],[-.87462,.48481,.647439,-.734431,.203551,-.360692,.838638,-.398086,.090085],[-.866025,.5,.625,-.75,.216506,-.32476,.842012,-.419263,.098821],[-.857167,.515038,.602104,-.764655,.229726,-.288728,.843265,-.440311,.108009],[-.848048,.529919,.578778,-.778378,.243192,-.252688,.842399,-.461164,.117644],[-.838671,.544639,.555052,-.791154,.256891,-.21673,.839422,-.481753,.127722],[-.829038,.559193,.530955,-.802965,.270803,-.180944,.834347,-.502011,.138237],[-.819152,.573576,.506515,-.813798,.284914,-.14542,.827194,-.521871,.149181],[-.809017,.587785,.481763,-.823639,.299204,-.110246,.817987,-.541266,.160545],[-.798636,.601815,.456728,-.832477,.313658,-.075508,.806757,-.560132,.172317],[-.788011,.615661,.431441,-.840301,.328257,-.041294,.793541,-.578405,.184487],[-.777146,.62932,.405934,-.847101,.342984,-.007686,.778379,-.596021,.19704],[-.766044,.642788,.380236,-.852869,.357821,.025233,.761319,-.612921,.209963],[-.75471,.656059,.35438,-.857597,.372749,.057383,.742412,-.629044,.223238],[-.743145,.669131,.328396,-.861281,.387751,.088686,.721714,-.644334,.23685],[-.731354,.681998,.302317,-.863916,.402807,.119068,.699288,-.658734,.250778],[-.71934,.694658,.276175,-.865498,.417901,.148454,.675199,-.67219,.265005],[-.707107,.707107,.25,-.866025,.433013,.176777,.649519,-.684653,.279508],[-.694658,.71934,.223825,-.865498,.448125,.203969,.622322,-.696073,.294267],[-.681998,.731354,.197683,-.863916,.463218,.229967,.593688,-.706405,.309259],[-.669131,.743145,.171604,-.861281,.478275,.254712,.5637,-.715605,.324459],[-.656059,.75471,.14562,-.857597,.493276,.278147,.532443,-.723633,.339844],[-.642788,.766044,.119764,-.852869,.508205,.300221,.500009,-.730451,.355387],[-.62932,.777146,.094066,-.847101,.523041,.320884,.46649,-.736025,.371063],[-.615661,.788011,.068559,-.840301,.537768,.340093,.431982,-.740324,.386845],[-.601815,.798636,.043272,-.832477,.552367,.357807,.396584,-.74332,.402704],[-.587785,.809017,.018237,-.823639,.566821,.373991,.360397,-.744989,.418613],[-.573576,.819152,-.006515,-.813798,.581112,.388612,.323524,-.745308,.434544],[-.559193,.829038,-.030955,-.802965,.595222,.401645,.286069,-.744262,.450467],[-.544639,.838671,-.055052,-.791154,.609135,.413066,.24814,-.741835,.466352],[-.529919,.848048,-.078778,-.778378,.622833,.422856,.209843,-.738017,.482171],[-.515038,.857167,-.102104,-.764655,.6363,.431004,.171288,-.732801,.497894],[-.5,.866025,-.125,-.75,.649519,.4375,.132583,-.726184,.51349],[-.48481,.87462,-.147439,-.734431,.662474,.44234,.093837,-.718167,.528929],[-.469472,.882948,-.169395,-.717968,.67515,.445524,.05516,-.708753,.544183],[-.45399,.891007,-.190839,-.700629,.687531,.447059,.016662,-.69795,.55922],[-.438371,.898794,-.211746,-.682437,.699602,.446953,-.02155,-.685769,.574011],[-.422618,.906308,-.232091,-.663414,.711348,.445222,-.059368,-.672226,.588528],[-.406737,.913545,-.251848,-.643582,.722755,.441884,-.096684,-.657339,.602741],[-.390731,.920505,-.270994,-.622967,.733809,.436964,-.133395,-.64113,.616621],[-.374607,.927184,-.289505,-.601592,.744496,.430488,-.169397,-.623624,.630141],[-.358368,.93358,-.307359,-.579484,.754804,.422491,-.204589,-.604851,.643273],[-.34202,.939693,-.324533,-.55667,.76472,.413008,-.238872,-.584843,.65599],[-.325568,.945519,-.341008,-.533178,.774231,.402081,-.27215,-.563635,.668267],[-.309017,.951057,-.356763,-.509037,.783327,.389754,-.304329,-.541266,.680078],[-.292372,.956305,-.371778,-.484275,.791997,.376077,-.335319,-.517778,.691399],[-.275637,.961262,-.386036,-.458924,.800228,.361102,-.365034,-.493216,.702207],[-.258819,.965926,-.399519,-.433013,.808013,.344885,-.393389,-.467627,.712478],[-.241922,.970296,-.412211,-.406574,.81534,.327486,-.420306,-.441061,.722191],[-.224951,.97437,-.424096,-.379641,.822202,.308969,-.445709,-.413572,.731327],[-.207912,.978148,-.435159,-.352244,.828589,.289399,-.469527,-.385215,.739866],[-.190809,.981627,-.445388,-.324419,.834495,.268846,-.491693,-.356047,.74779],[-.173648,.984808,-.454769,-.296198,.839912,.247382,-.512145,-.326129,.755082],[-.156434,.987688,-.463292,-.267617,.844832,.225081,-.530827,-.295521,.761728],[-.139173,.990268,-.470946,-.238709,.849251,.20202,-.547684,-.264287,.767712],[-.121869,.992546,-.477722,-.209511,.853163,.178279,-.562672,-.232494,.773023],[-.104528,.994522,-.483611,-.180057,.856563,.153937,-.575747,-.200207,.777648],[-.087156,.996195,-.488606,-.150384,.859447,.129078,-.586872,-.167494,.781579],[-.069756,.997564,-.492701,-.120527,.861811,.103786,-.596018,-.134426,.784806],[-.052336,.99863,-.495891,-.090524,.863653,.078146,-.603158,-.101071,.787324],[-.034899,.999391,-.498173,-.060411,.864971,.052243,-.608272,-.0675,.789126],[-.017452,.999848,-.499543,-.030224,.865762,.026165,-.611347,-.033786,.790208],[0,1,-.5,0,.866025,-0,-.612372,0,.790569],[.017452,.999848,-.499543,.030224,.865762,-.026165,-.611347,.033786,.790208],[.034899,.999391,-.498173,.060411,.864971,-.052243,-.608272,.0675,.789126],[.052336,.99863,-.495891,.090524,.863653,-.078146,-.603158,.101071,.787324],[.069756,.997564,-.492701,.120527,.861811,-.103786,-.596018,.134426,.784806],[.087156,.996195,-.488606,.150384,.859447,-.129078,-.586872,.167494,.781579],[.104528,.994522,-.483611,.180057,.856563,-.153937,-.575747,.200207,.777648],[.121869,.992546,-.477722,.209511,.853163,-.178279,-.562672,.232494,.773023],[.139173,.990268,-.470946,.238709,.849251,-.20202,-.547684,.264287,.767712],[.156434,.987688,-.463292,.267617,.844832,-.225081,-.530827,.295521,.761728],[.173648,.984808,-.454769,.296198,.839912,-.247382,-.512145,.326129,.755082],[.190809,.981627,-.445388,.324419,.834495,-.268846,-.491693,.356047,.74779],[.207912,.978148,-.435159,.352244,.828589,-.289399,-.469527,.385215,.739866],[.224951,.97437,-.424096,.379641,.822202,-.308969,-.445709,.413572,.731327],[.241922,.970296,-.412211,.406574,.81534,-.327486,-.420306,.441061,.722191],[.258819,.965926,-.399519,.433013,.808013,-.344885,-.393389,.467627,.712478],[.275637,.961262,-.386036,.458924,.800228,-.361102,-.365034,.493216,.702207],[.292372,.956305,-.371778,.484275,.791997,-.376077,-.335319,.517778,.691399],[.309017,.951057,-.356763,.509037,.783327,-.389754,-.304329,.541266,.680078],[.325568,.945519,-.341008,.533178,.774231,-.402081,-.27215,.563635,.668267],[.34202,.939693,-.324533,.55667,.76472,-.413008,-.238872,.584843,.65599],[.358368,.93358,-.307359,.579484,.754804,-.422491,-.204589,.604851,.643273],[.374607,.927184,-.289505,.601592,.744496,-.430488,-.169397,.623624,.630141],[.390731,.920505,-.270994,.622967,.733809,-.436964,-.133395,.64113,.616621],[.406737,.913545,-.251848,.643582,.722755,-.441884,-.096684,.657339,.602741],[.422618,.906308,-.232091,.663414,.711348,-.445222,-.059368,.672226,.588528],[.438371,.898794,-.211746,.682437,.699602,-.446953,-.02155,.685769,.574011],[.45399,.891007,-.190839,.700629,.687531,-.447059,.016662,.69795,.55922],[.469472,.882948,-.169395,.717968,.67515,-.445524,.05516,.708753,.544183],[.48481,.87462,-.147439,.734431,.662474,-.44234,.093837,.718167,.528929],[.5,.866025,-.125,.75,.649519,-.4375,.132583,.726184,.51349],[.515038,.857167,-.102104,.764655,.6363,-.431004,.171288,.732801,.497894],[.529919,.848048,-.078778,.778378,.622833,-.422856,.209843,.738017,.482171],[.544639,.838671,-.055052,.791154,.609135,-.413066,.24814,.741835,.466352],[.559193,.829038,-.030955,.802965,.595222,-.401645,.286069,.744262,.450467],[.573576,.819152,-.006515,.813798,.581112,-.388612,.323524,.745308,.434544],[.587785,.809017,.018237,.823639,.566821,-.373991,.360397,.744989,.418613],[.601815,.798636,.043272,.832477,.552367,-.357807,.396584,.74332,.402704],[.615661,.788011,.068559,.840301,.537768,-.340093,.431982,.740324,.386845],[.62932,.777146,.094066,.847101,.523041,-.320884,.46649,.736025,.371063],[.642788,.766044,.119764,.852869,.508205,-.300221,.500009,.730451,.355387],[.656059,.75471,.14562,.857597,.493276,-.278147,.532443,.723633,.339844],[.669131,.743145,.171604,.861281,.478275,-.254712,.5637,.715605,.324459],[.681998,.731354,.197683,.863916,.463218,-.229967,.593688,.706405,.309259],[.694658,.71934,.223825,.865498,.448125,-.203969,.622322,.696073,.294267],[.707107,.707107,.25,.866025,.433013,-.176777,.649519,.684653,.279508],[.71934,.694658,.276175,.865498,.417901,-.148454,.675199,.67219,.265005],[.731354,.681998,.302317,.863916,.402807,-.119068,.699288,.658734,.250778],[.743145,.669131,.328396,.861281,.387751,-.088686,.721714,.644334,.23685],[.75471,.656059,.35438,.857597,.372749,-.057383,.742412,.629044,.223238],[.766044,.642788,.380236,.852869,.357821,-.025233,.761319,.612921,.209963],[.777146,.62932,.405934,.847101,.342984,.007686,.778379,.596021,.19704],[.788011,.615661,.431441,.840301,.328257,.041294,.793541,.578405,.184487],[.798636,.601815,.456728,.832477,.313658,.075508,.806757,.560132,.172317],[.809017,.587785,.481763,.823639,.299204,.110246,.817987,.541266,.160545],[.819152,.573576,.506515,.813798,.284914,.14542,.827194,.521871,.149181],[.829038,.559193,.530955,.802965,.270803,.180944,.834347,.502011,.138237],[.838671,.544639,.555052,.791154,.256891,.21673,.839422,.481753,.127722],[.848048,.529919,.578778,.778378,.243192,.252688,.842399,.461164,.117644],[.857167,.515038,.602104,.764655,.229726,.288728,.843265,.440311,.108009],[.866025,.5,.625,.75,.216506,.32476,.842012,.419263,.098821],[.87462,.48481,.647439,.734431,.203551,.360692,.838638,.398086,.090085],[.882948,.469472,.669395,.717968,.190875,.396436,.833145,.376851,.081803],[.891007,.45399,.690839,.700629,.178494,.431899,.825544,.355623,.073974],[.898794,.438371,.711746,.682437,.166423,.466993,.81585,.334472,.066599],[.906308,.422618,.732091,.663414,.154678,.501627,.804083,.313464,.059674],[.913545,.406737,.751848,.643582,.143271,.535715,.79027,.292666,.053196],[.920505,.390731,.770994,.622967,.132217,.569169,.774442,.272143,.04716],[.927184,.374607,.789505,.601592,.121529,.601904,.756637,.25196,.041559],[.93358,.358368,.807359,.579484,.111222,.633837,.736898,.23218,.036385],[.939693,.34202,.824533,.55667,.101306,.664885,.715274,.212865,.03163],[.945519,.325568,.841008,.533178,.091794,.694969,.691816,.194075,.027281],[.951057,.309017,.856763,.509037,.082698,.724012,.666583,.175868,.023329],[.956305,.292372,.871778,.484275,.074029,.75194,.639639,.158301,.019758],[.961262,.275637,.886036,.458924,.065797,.77868,.61105,.141427,.016556],[.965926,.258819,.899519,.433013,.058013,.804164,.580889,.1253,.013707],[.970296,.241922,.912211,.406574,.050685,.828326,.549233,.109969,.011193],[.97437,.224951,.924096,.379641,.043823,.851105,.516162,.095481,.008999],[.978148,.207912,.935159,.352244,.037436,.872441,.481759,.08188,.007105],[.981627,.190809,.945388,.324419,.03153,.892279,.446114,.069209,.005492],[.984808,.173648,.954769,.296198,.026114,.910569,.409317,.057505,.00414],[.987688,.156434,.963292,.267617,.021193,.927262,.371463,.046806,.003026],[.990268,.139173,.970946,.238709,.016774,.942316,.332649,.037143,.002131],[.992546,.121869,.977722,.209511,.012862,.955693,.292976,.028547,.001431],[.994522,.104528,.983611,.180057,.009462,.967356,.252544,.021043,903e-6],[.996195,.087156,.988606,.150384,.006578,.977277,.21146,.014654,523e-6],[.997564,.069756,.992701,.120527,.004214,.985429,.169828,.0094,268e-6],[.99863,.052336,.995891,.090524,.002372,.991791,.127757,.005297,113e-6],[.999391,.034899,.998173,.060411,.001055,.996348,.085356,.002357,34e-6],[.999848,.017452,.999543,.030224,264e-6,.999086,.042733,59e-5,4e-6],[1,-0,1,-0,0,1,-0,0,-0]]],t.SPHERICAL_HARMONICS_AZIMUTH_RESOLUTION=t.SPHERICAL_HARMONICS[0].length,t.SPHERICAL_HARMONICS_ELEVATION_RESOLUTION=t.SPHERICAL_HARMONICS[1].length,t.SPHERICAL_HARMONICS_MAX_ORDER=t.SPHERICAL_HARMONICS[0][0].length/2,t.MAX_RE_WEIGHTS=[[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,1,1],[1.003236,1.002156,.999152,.990038],[1.03237,1.021194,.990433,.898572],[1.062694,1.040231,.979161,.799806],[1.093999,1.058954,.964976,.693603],[1.126003,1.077006,.947526,.57989],[1.158345,1.093982,.926474,.45869],[1.19059,1.109437,.901512,.330158],[1.222228,1.12289,.87237,.194621],[1.252684,1.133837,.838839,.052614],[1.281987,1.142358,.801199,0],[1.312073,1.150207,.760839,0],[1.343011,1.157424,.717799,0],[1.374649,1.163859,.671999,0],[1.406809,1.169354,.623371,0],[1.439286,1.173739,.571868,0],[1.471846,1.176837,.517465,0],[1.504226,1.178465,.460174,0],[1.536133,1.178438,.400043,0],[1.567253,1.176573,.337165,0],[1.597247,1.172695,.271688,0],[1.625766,1.166645,.203815,0],[1.652455,1.158285,.133806,0],[1.676966,1.147506,.061983,0],[1.699006,1.134261,0,0],[1.720224,1.119789,0,0],[1.741631,1.10481,0,0],[1.763183,1.08933,0,0],[1.784837,1.073356,0,0],[1.806548,1.056898,0,0],[1.828269,1.039968,0,0],[1.849952,1.02258,0,0],[1.871552,1.004752,0,0],[1.893018,.986504,0,0],[1.914305,.967857,0,0],[1.935366,.948837,0,0],[1.956154,.929471,0,0],[1.976625,.90979,0,0],[1.996736,.889823,0,0],[2.016448,.869607,0,0],[2.035721,.849175,0,0],[2.054522,.828565,0,0],[2.072818,.807816,0,0],[2.090581,.786964,0,0],[2.107785,.766051,0,0],[2.124411,.745115,0,0],[2.140439,.724196,0,0],[2.155856,.703332,0,0],[2.170653,.682561,0,0],[2.184823,.661921,0,0],[2.198364,.641445,0,0],[2.211275,.621169,0,0],[2.223562,.601125,0,0],[2.23523,.581341,0,0],[2.246289,.561847,0,0],[2.256751,.542667,0,0],[2.266631,.523826,0,0],[2.275943,.505344,0,0],[2.284707,.487239,0,0],[2.292939,.469528,0,0],[2.300661,.452225,0,0],[2.307892,.435342,0,0],[2.314654,.418888,0,0],[2.320969,.40287,0,0],[2.326858,.387294,0,0],[2.332343,.372164,0,0],[2.337445,.357481,0,0],[2.342186,.343246,0,0],[2.346585,.329458,0,0],[2.350664,.316113,0,0],[2.354442,.303208,0,0],[2.357937,.290738,0,0],[2.361168,.278698,0,0],[2.364152,.26708,0,0],[2.366906,.255878,0,0],[2.369446,.245082,0,0],[2.371786,.234685,0,0],[2.37394,.224677,0,0],[2.375923,.215048,0,0],[2.377745,.20579,0,0],[2.379421,.196891,0,0],[2.380959,.188342,0,0],[2.382372,.180132,0,0],[2.383667,.172251,0,0],[2.384856,.164689,0,0],[2.385945,.157435,0,0],[2.386943,.150479,0,0],[2.387857,.143811,0,0],[2.388694,.137421,0,0],[2.38946,.131299,0,0],[2.39016,.125435,0,0],[2.390801,.11982,0,0],[2.391386,.114445,0,0],[2.391921,.1093,0,0],[2.39241,.104376,0,0],[2.392857,.099666,0,0],[2.393265,.09516,0,0],[2.393637,.090851,0,0],[2.393977,.086731,0,0],[2.394288,.082791,0,0],[2.394571,.079025,0,0],[2.394829,.075426,0,0],[2.395064,.071986,0,0],[2.395279,.068699,0,0],[2.395475,.065558,0,0],[2.395653,.062558,0,0],[2.395816,.059693,0,0],[2.395964,.056955,0,0],[2.396099,.054341,0,0],[2.396222,.051845,0,0],[2.396334,.049462,0,0],[2.396436,.047186,0,0],[2.396529,.045013,0,0],[2.396613,.042939,0,0],[2.396691,.040959,0,0],[2.396761,.039069,0,0],[2.396825,.037266,0,0],[2.396883,.035544,0,0],[2.396936,.033901,0,0],[2.396984,.032334,0,0],[2.397028,.030838,0,0],[2.397068,.02941,0,0],[2.397104,.028048,0,0],[2.397137,.026749,0,0],[2.397167,.025509,0,0],[2.397194,.024326,0,0],[2.397219,.023198,0,0],[2.397242,.022122,0,0],[2.397262,.021095,0,0],[2.397281,.020116,0,0],[2.397298,.019181,0,0],[2.397314,.01829,0,0],[2.397328,.017441,0,0],[2.397341,.01663,0,0],[2.397352,.015857,0,0],[2.397363,.015119,0,0],[2.397372,.014416,0,0],[2.397381,.013745,0,0],[2.397389,.013106,0,0],[2.397396,.012496,0,0],[2.397403,.011914,0,0],[2.397409,.01136,0,0],[2.397414,.010831,0,0],[2.397419,.010326,0,0],[2.397424,.009845,0,0],[2.397428,.009387,0,0],[2.397432,.008949,0,0],[2.397435,.008532,0,0],[2.397438,.008135,0,0],[2.397441,.007755,0,0],[2.397443,.007394,0,0],[2.397446,.007049,0,0],[2.397448,.006721,0,0],[2.39745,.006407,0,0],[2.397451,.006108,0,0],[2.397453,.005824,0,0],[2.397454,.005552,0,0],[2.397456,.005293,0,0],[2.397457,.005046,0,0],[2.397458,.004811,0,0],[2.397459,.004586,0,0],[2.39746,.004372,0,0],[2.397461,.004168,0,0],[2.397461,.003974,0,0],[2.397462,.003788,0,0],[2.397463,.003611,0,0],[2.397463,.003443,0,0],[2.397464,.003282,0,0],[2.397464,.003129,0,0],[2.397465,.002983,0,0],[2.397465,.002844,0,0],[2.397465,.002711,0,0],[2.397466,.002584,0,0],[2.397466,.002464,0,0],[2.397466,.002349,0,0],[2.397466,.002239,0,0],[2.397467,.002135,0,0],[2.397467,.002035,0,0],[2.397467,.00194,0,0],[2.397467,.001849,0,0],[2.397467,.001763,0,0],[2.397467,.001681,0,0],[2.397468,.001602,0,0],[2.397468,.001527,0,0],[2.397468,.001456,0,0],[2.397468,.001388,0,0],[2.397468,.001323,0,0],[2.397468,.001261,0,0],[2.397468,.001202,0,0],[2.397468,.001146,0,0],[2.397468,.001093,0,0],[2.397468,.001042,0,0],[2.397468,993e-6,0,0],[2.397468,947e-6,0,0],[2.397468,902e-6,0,0],[2.397468,86e-5,0,0],[2.397468,82e-5,0,0],[2.397469,782e-6,0,0],[2.397469,745e-6,0,0],[2.397469,71e-5,0,0],[2.397469,677e-6,0,0],[2.397469,646e-6,0,0],[2.397469,616e-6,0,0],[2.397469,587e-6,0,0],[2.397469,559e-6,0,0],[2.397469,533e-6,0,0],[2.397469,508e-6,0,0],[2.397469,485e-6,0,0],[2.397469,462e-6,0,0],[2.397469,44e-5,0,0],[2.397469,42e-5,0,0],[2.397469,4e-4,0,0],[2.397469,381e-6,0,0],[2.397469,364e-6,0,0],[2.397469,347e-6,0,0],[2.397469,33e-5,0,0],[2.397469,315e-6,0,0],[2.397469,3e-4,0,0],[2.397469,286e-6,0,0],[2.397469,273e-6,0,0],[2.397469,26e-5,0,0],[2.397469,248e-6,0,0],[2.397469,236e-6,0,0],[2.397469,225e-6,0,0],[2.397469,215e-6,0,0],[2.397469,205e-6,0,0],[2.397469,195e-6,0,0],[2.397469,186e-6,0,0],[2.397469,177e-6,0,0],[2.397469,169e-6,0,0],[2.397469,161e-6,0,0],[2.397469,154e-6,0,0],[2.397469,147e-6,0,0],[2.397469,14e-5,0,0],[2.397469,133e-6,0,0],[2.397469,127e-6,0,0],[2.397469,121e-6,0,0],[2.397469,115e-6,0,0],[2.397469,11e-5,0,0],[2.397469,105e-6,0,0],[2.397469,1e-4,0,0],[2.397469,95e-6,0,0],[2.397469,91e-6,0,0],[2.397469,87e-6,0,0],[2.397469,83e-6,0,0],[2.397469,79e-6,0,0],[2.397469,75e-6,0,0],[2.397469,71e-6,0,0],[2.397469,68e-6,0,0],[2.397469,65e-6,0,0],[2.397469,62e-6,0,0],[2.397469,59e-6,0,0],[2.397469,56e-6,0,0],[2.397469,54e-6,0,0],[2.397469,51e-6,0,0],[2.397469,49e-6,0,0],[2.397469,46e-6,0,0],[2.397469,44e-6,0,0],[2.397469,42e-6,0,0],[2.397469,4e-5,0,0],[2.397469,38e-6,0,0],[2.397469,37e-6,0,0],[2.397469,35e-6,0,0],[2.397469,33e-6,0,0],[2.397469,32e-6,0,0],[2.397469,3e-5,0,0],[2.397469,29e-6,0,0],[2.397469,27e-6,0,0],[2.397469,26e-6,0,0],[2.397469,25e-6,0,0],[2.397469,24e-6,0,0],[2.397469,23e-6,0,0],[2.397469,22e-6,0,0],[2.397469,21e-6,0,0],[2.397469,2e-5,0,0],[2.397469,19e-6,0,0],[2.397469,18e-6,0,0],[2.397469,17e-6,0,0],[2.397469,16e-6,0,0],[2.397469,15e-6,0,0],[2.397469,15e-6,0,0],[2.397469,14e-6,0,0],[2.397469,13e-6,0,0],[2.397469,13e-6,0,0],[2.397469,12e-6,0,0],[2.397469,12e-6,0,0],[2.397469,11e-6,0,0],[2.397469,11e-6,0,0],[2.397469,1e-5,0,0],[2.397469,1e-5,0,0],[2.397469,9e-6,0,0],[2.397469,9e-6,0,0],[2.397469,8e-6,0,0],[2.397469,8e-6,0,0],[2.397469,8e-6,0,0],[2.397469,7e-6,0,0],[2.397469,7e-6,0,0],[2.397469,7e-6,0,0],[2.397469,6e-6,0,0],[2.397469,6e-6,0,0],[2.397469,6e-6,0,0],[2.397469,5e-6,0,0],[2.397469,5e-6,0,0],[2.397469,5e-6,0,0],[2.397469,5e-6,0,0],[2.397469,4e-6,0,0],[2.397469,4e-6,0,0],[2.397469,4e-6,0,0],[2.397469,4e-6,0,0],[2.397469,4e-6,0,0],[2.397469,4e-6,0,0],[2.397469,3e-6,0,0],[2.397469,3e-6,0,0],[2.397469,3e-6,0,0],[2.397469,3e-6,0,0],[2.397469,3e-6,0,0],[2.397469,3e-6,0,0],[2.397469,3e-6,0,0],[2.397469,2e-6,0,0],[2.397469,2e-6,0,0],[2.397469,2e-6,0,0],[2.397469,2e-6,0,0],[2.397469,2e-6,0,0],[2.397469,2e-6,0,0],[2.397469,2e-6,0,0],[2.397469,2e-6,0,0],[2.397469,2e-6,0,0],[2.397469,2e-6,0,0],[2.397469,1e-6,0,0],[2.397469,1e-6,0,0],[2.397469,1e-6,0,0]],t.MAX_RE_WEIGHTS_RESOLUTION=t.MAX_RE_WEIGHTS.length},function(A,t,e){"use strict";function i(A,t){void 0==t&&(t={}),void 0==t.position&&(t.position=a.DEFAULT_POSITION.slice()),void 0==t.forward&&(t.forward=a.DEFAULT_FORWARD.slice()),void 0==t.up&&(t.up=a.DEFAULT_UP.slice()),void 0==t.minDistance&&(t.minDistance=a.DEFAULT_MIN_DISTANCE),void 0==t.maxDistance&&(t.maxDistance=a.DEFAULT_MAX_DISTANCE),void 0==t.rolloff&&(t.rolloff=a.DEFAULT_ROLLOFF),void 0==t.gain&&(t.gain=a.DEFAULT_SOURCE_GAIN),void 0==t.alpha&&(t.alpha=a.DEFAULT_DIRECTIVITY_ALPHA),void 0==t.sharpness&&(t.sharpness=a.DEFAULT_DIRECTIVITY_SHARPNESS),void 0==t.sourceWidth&&(t.sourceWidth=a.DEFAULT_SOURCE_WIDTH),this._scene=A,this._position=t.position,this._forward=t.forward,this._up=t.up,this._dx=new Float32Array(3),this._right=a.crossProduct(this._forward,this._up);let e=A._context;this.input=e.createGain(),this._directivity=new s(e,{alpha:t.alpha,sharpness:t.sharpness}),this._toEarly=e.createGain(),this._toLate=e.createGain(),this._attenuation=new o(e,{minDistance:t.minDistance,maxDistance:t.maxDistance,rolloff:t.rolloff}),this._encoder=new r(e,{ambisonicOrder:A._ambisonicOrder,sourceWidth:t.sourceWidth}),this.input.connect(this._toLate),this._toLate.connect(A._room.late.input),this.input.connect(this._attenuation.input),this._attenuation.output.connect(this._toEarly),this._toEarly.connect(A._room.early.input),this._attenuation.output.connect(this._directivity.input),this._directivity.output.connect(this._encoder.input),this._encoder.output.connect(A._listener.input),this.setPosition(t.position[0],t.position[1],t.position[2]),this.input.gain.value=t.gain}function n(A){let t=1;return A>a.EPSILON_FLOAT&&(t=1-A/a.SOURCE_MAX_OUTSIDE_ROOM_DISTANCE,t=Math.max(0,Math.min(1,t))),t}/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const s=e(5),o=e(6),r=e(1),a=e(0);i.prototype.setPosition=function(A,t,e){this._position[0]=A,this._position[1]=t,this._position[2]=e;let i=n(this._scene._room.getDistanceOutsideRoom(this._position[0],this._position[1],this._position[2]));this._toLate.gain.value=i,this._toEarly.gain.value=i,this._update()},i.prototype._update=function(){for(let i=0;i<3;i++)this._dx[i]=this._position[i]-this._scene._listener.position[i];let A=Math.sqrt(this._dx[0]*this._dx[0]+this._dx[1]*this._dx[1]+this._dx[2]*this._dx[2]);A>0&&(this._dx[0]/=A,this._dx[1]/=A,this._dx[2]/=A);let t=Math.atan2(-this._dx[0],this._dx[2])*a.RADIANS_TO_DEGREES,e=Math.atan2(this._dx[1],Math.sqrt(this._dx[0]*this._dx[0]+this._dx[2]*this._dx[2]))*a.RADIANS_TO_DEGREES;this._attenuation.setDistance(A),this._directivity.computeAngle(this._forward,this._dx),this._encoder.setDirection(t,e)},i.prototype.setRolloff=function(A){this._attenuation.setRolloff(A)},i.prototype.setMinDistance=function(A){this._attenuation.minDistance=A},i.prototype.setMaxDistance=function(A){this._attenuation.maxDistance=A},i.prototype.setGain=function(A){this.input.gain.value=A},i.prototype.setOrientation=function(A,t,e,i,n,s){this._forward[0]=A,this._forward[1]=t,this._forward[2]=e,this._up[0]=i,this._up[1]=n,this._up[2]=s,this._right=a.crossProduct(this._forward,this._up)},i.prototype.setFromMatrix=function(A){this._right[0]=A.elements[0],this._right[1]=A.elements[1],this._right[2]=A.elements[2],this._up[0]=A.elements[4],this._up[1]=A.elements[5],this._up[2]=A.elements[6],this._forward[0]=A.elements[8],this._forward[1]=A.elements[9],this._forward[2]=A.elements[10],this._right=a.normalizeVector(this._right),this._up=a.normalizeVector(this._up),this._forward=a.normalizeVector(this._forward),this.setPosition(A.elements[12],A.elements[13],A.elements[14])},i.prototype.setSourceWidth=function(A){this._encoder.setSourceWidth(A),this.setPosition(this._position[0],this._position[1],this._position[2])},i.prototype.setDirectivityPattern=function(A,t){this._directivity.setPattern(A,t),this.setPosition(this._position[0],this._position[1],this._position[2])},A.exports=i},function(A,t,e){"use strict";function i(A,t){void 0==t&&(t={}),void 0==t.alpha&&(t.alpha=n.DEFAULT_DIRECTIVITY_ALPHA),void 0==t.sharpness&&(t.sharpness=n.DEFAULT_DIRECTIVITY_SHARPNESS),this._context=A,this._lowpass=A.createBiquadFilter(),this._lowpass.type="lowpass",this._lowpass.Q.value=0,this._lowpass.frequency.value=.5*A.sampleRate,this._cosTheta=0,this.setPattern(t.alpha,t.sharpness),this.input=this._lowpass,this.output=this._lowpass}/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const n=e(0);i.prototype.computeAngle=function(A,t){let e=n.normalizeVector(A),i=n.normalizeVector(t),s=1;if(this._alpha>n.EPSILON_FLOAT){let A=e[0]*i[0]+e[1]*i[1]+e[2]*i[2];s=1-this._alpha+this._alpha*A,s=Math.pow(Math.abs(s),this._sharpness)}this._lowpass.frequency.value=.5*this._context.sampleRate*s},i.prototype.setPattern=function(A,t){this._alpha=Math.min(1,Math.max(0,A)),this._sharpness=Math.max(1,t),this.computeAngle([this._cosTheta*this._cosTheta,0,0],[1,0,0])},A.exports=i},function(A,t,e){"use strict";function i(A,t){void 0==t&&(t={}),void 0==t.minDistance&&(t.minDistance=n.DEFAULT_MIN_DISTANCE),void 0==t.maxDistance&&(t.maxDistance=n.DEFAULT_MAX_DISTANCE),void 0==t.rolloff&&(t.rolloff=n.DEFAULT_ATTENUATION_ROLLOFF),this.minDistance=t.minDistance,this.maxDistance=t.maxDistance,this.setRolloff(t.rolloff),this._gainNode=A.createGain(),this.setDistance(t.maxDistance),this.input=this._gainNode,this.output=this._gainNode}/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const n=e(0);i.prototype.setDistance=function(A){let t=1;if("logarithmic"==this._rolloff){if(A>this.maxDistance)t=0;else if(A>this.minDistance){let e=this.maxDistance-this.minDistance;if(e>n.EPSILON_FLOAT){let i=1/(e+1);t=(1/(A-this.minDistance+1)-i)/(1-i)}}}else if("linear"==this._rolloff)if(A>this.maxDistance)t=0;else if(A>this.minDistance){let e=this.maxDistance-this.minDistance;e>n.EPSILON_FLOAT&&(t=(this.maxDistance-A)/e)}this._gainNode.gain.value=t},i.prototype.setRolloff=function(A){let t=~n.ATTENUATION_ROLLOFFS.indexOf(A);void 0!=A&&t?A=A.toString().toLowerCase():(t||n.log('Invalid rolloff model ("'+A+'"). Using default: "'+n.DEFAULT_ATTENUATION_ROLLOFF+'".'),A=n.DEFAULT_ATTENUATION_ROLLOFF),this._rolloff=A},A.exports=i},function(A,t,e){"use strict";function i(A){let t={};for(let e in _.DEFAULT_ROOM_MATERIALS)_.DEFAULT_ROOM_MATERIALS.hasOwnProperty(e)&&(t[e]=_.ROOM_MATERIAL_COEFFICIENTS[_.DEFAULT_ROOM_MATERIALS[e]]);void 0==A&&(A={},Object.assign(A,_.DEFAULT_ROOM_MATERIALS));for(let e in _.DEFAULT_ROOM_MATERIALS)_.DEFAULT_ROOM_MATERIALS.hasOwnProperty(e)&&A.hasOwnProperty(e)?A[e]in _.ROOM_MATERIAL_COEFFICIENTS?t[e]=_.ROOM_MATERIAL_COEFFICIENTS[A[e]]:_.log('Material "'+A[e]+'" on wall "'+e+'" not found. Using "'+_.DEFAULT_ROOM_MATERIALS[e]+'".'):_.log('Wall "'+e+'" is not defined. Default used.');return t}function n(A){void 0==A&&(A={});for(let t in _.DEFAULT_ROOM_MATERIALS)A.hasOwnProperty(t)||(A[t]=_.ROOM_MATERIAL_COEFFICIENTS[_.DEFAULT_ROOM_MATERIALS[t]]);return A}function s(A){void 0==A&&(A={});for(let t in _.DEFAULT_ROOM_DIMENSIONS)A.hasOwnProperty(t)||(A[t]=_.DEFAULT_ROOM_DIMENSIONS[t]);return A}function o(A,t,e){let i=new Float32Array(_.NUMBER_REVERB_FREQUENCY_BANDS);A=s(A),t=n(t),void 0==e&&(e=_.DEFAULT_SPEED_OF_SOUND);let o=_.TWENTY_FOUR_LOG10/e,r=A.width*A.height*A.depth;if(r<_.ROOM_MIN_VOLUME)return i;let a=A.width*A.height,h=A.width*A.depth,c=A.depth*A.height,f=2*(a+h+c);for(let n=0;n<_.NUMBER_REVERB_FREQUENCY_BANDS;n++){let A=((t.left[n]+t.right[n])*a+(t.down[n]+t.up[n])*h+(t.front[n]+t.back[n])*c)/f;i[n]=_.ROOM_EYRING_CORRECTION_COEFFICIENT*o*r/(-f*Math.log(1-A)+4*_.ROOM_AIR_ABSORPTION_COEFFICIENTS[n]*r)}return i}function r(A){let t=[];for(let e in _.DEFAULT_REFLECTION_COEFFICIENTS)if(_.DEFAULT_REFLECTION_COEFFICIENTS.hasOwnProperty(e)){t[e]=0;for(let i=0;i<_.NUMBER_REFLECTION_AVERAGING_BANDS;i++){let n=i+_.ROOM_STARTING_AVERAGING_BAND;t[e]+=A[e][n]}t[e]/=_.NUMBER_REFLECTION_AVERAGING_BANDS,t[e]=Math.sqrt(1-t[e])}return t}function a(A,t){void 0==t&&(t={}),void 0==t.listenerPosition&&(t.listenerPosition=_.DEFAULT_POSITION.slice()),void 0==t.dimensions&&(t.dimensions={},Object.assign(t.dimensions,_.DEFAULT_ROOM_DIMENSIONS)),void 0==t.materials&&(t.materials={},Object.assign(t.materials,_.DEFAULT_ROOM_MATERIALS)),void 0==t.speedOfSound&&(t.speedOfSound=_.DEFAULT_SPEED_OF_SOUND),t.dimensions=s(t.dimensions);let e=i(t.materials),n=r(e),a=o(t.dimensions,e,t.speedOfSound);this.early=new c(A,{dimensions:t.dimensions,coefficients:n,speedOfSound:t.speedOfSound,listenerPosition:t.listenerPosition}),this.late=new h(A,{durations:a}),this.speedOfSound=t.speedOfSound,this.output=A.createGain(),this.early.output.connect(this.output),this._merger=A.createChannelMerger(4),this.late.output.connect(this._merger,0,0),this._merger.connect(this.output)}/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const h=e(8),c=e(9),_=e(0);a.prototype.setProperties=function(A,t){let e=i(t),n=o(A,e,this.speedOfSound);this.late.setDurations(n),this.early.speedOfSound=this.speedOfSound;let s=r(e);this.early.setRoomProperties(A,s)},a.prototype.setListenerPosition=function(A,t,e){this.early.speedOfSound=this.speedOfSound,this.early.setListenerPosition(A,t,e);let i=this.getDistanceOutsideRoom(A,t,e),n=1;i>_.EPSILON_FLOAT&&(n=1-i/_.LISTENER_MAX_OUTSIDE_ROOM_DISTANCE,n=Math.max(0,Math.min(1,n))),this.output.gain.value=n},a.prototype.getDistanceOutsideRoom=function(A,t,e){let i=Math.max(0,-this.early._halfDimensions.width-A,A-this.early._halfDimensions.width),n=Math.max(0,-this.early._halfDimensions.height-t,t-this.early._halfDimensions.height),s=Math.max(0,-this.early._halfDimensions.depth-e,e-this.early._halfDimensions.depth);return Math.sqrt(i*i+n*n+s*s)},A.exports=a},function(A,t,e){"use strict";function i(A,t){void 0==t&&(t={}),void 0==t.durations&&(t.durations=n.DEFAULT_REVERB_DURATIONS.slice()),void 0==t.predelay&&(t.predelay=n.DEFAULT_REVERB_PREDELAY),void 0==t.gain&&(t.gain=n.DEFAULT_REVERB_GAIN),void 0==t.bandwidth&&(t.bandwidth=n.DEFAULT_REVERB_BANDWIDTH),void 0==t.tailonset&&(t.tailonset=n.DEFAULT_REVERB_TAIL_ONSET);let e=t.predelay/1e3;this._bandwidthCoeff=t.bandwidth*n.LOG2_DIV2,this._tailonsetSamples=t.tailonset/1e3,this._context=A,this.input=A.createGain(),this._predelay=A.createDelay(e),this._convolver=A.createConvolver(),this.output=A.createGain(),this.output.gain.value=t.gain,this._convolver.normalize=!1,this.input.connect(this._predelay),this._predelay.connect(this._convolver),this._convolver.connect(this.output),this.setDurations(t.durations)}/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const n=e(0);i.prototype.setDurations=function(A){if(A.length!==n.NUMBER_REVERB_FREQUENCY_BANDS)return void n.log("Warning: invalid number of RT60 values provided to reverb.");let t=new Float32Array(n.NUMBER_REVERB_FREQUENCY_BANDS),e=this._context.sampleRate;for(let h=0;hi&&(i=t[n]);i<1&&(i=1);let s=this._context.createBuffer(1,i,e),o=s.getChannelData(0),r=new Float32Array(i);for(let n=0;n32&&t.throw("Utils.mergeBuffer: Number of channels cannot exceed 32.(got "+s+")"),i!==e[a].length&&t.throw("Utils.mergeBuffer: AudioBuffer lengths are inconsistent. (expected "+i+" but got "+e[a].length+")"),n!==e[a].sampleRate&&t.throw("Utils.mergeBuffer: AudioBuffer sample rates are inconsistent. (expected "+n+" but got "+e[a].sampleRate+")"),s+=e[a].numberOfChannels;const o=A.createBuffer(s,i,n);let r=0;for(let t=0;te[i]=t.charCodeAt(i)),e.buffer}},function(A,t,e){"use strict";function i(A,t,e){this._context=n.isAudioContext(A)?A:n.throw("BufferList: Invalid BaseAudioContext."),this._options={dataType:s.BASE64,verbose:!1},e&&(e.dataType&&n.isDefinedENUMEntry(s,e.dataType)&&(this._options.dataType=e.dataType),e.verbose&&(this._options.verbose=Boolean(e.verbose))),this._bufferList=[],this._bufferData=this._options.dataType===s.BASE64?t:t.slice(0),this._numberOfTasks=this._bufferData.length,this._resolveHandler=null,this._rejectHandler=new Function}const n=e(0),s={BASE64:"base64",URL:"url"};i.prototype.load=function(){return new Promise(this._promiseGenerator.bind(this))},i.prototype._promiseGenerator=function(A,t){"function"!=typeof A?n.throw("BufferList: Invalid Promise resolver."):this._resolveHandler=A,"function"==typeof t&&(this._rejectHandler=t);for(let e=0;e=0?this._stereoSplitters[i].connect(this._positiveIndexSphericalHarmonics,e%2):this._stereoSplitters[i].connect(this._negativeIndexSphericalHarmonics,e%2)}this._positiveIndexSphericalHarmonics.connect(this._binauralMerger,0,0),this._positiveIndexSphericalHarmonics.connect(this._binauralMerger,0,1),this._negativeIndexSphericalHarmonics.connect(this._binauralMerger,0,0),this._negativeIndexSphericalHarmonics.connect(this._inverter),this._inverter.connect(this._binauralMerger,0,1),this._inverter.gain.value=-1,this.input=this._inputSplitter,this.output=this._outputGain},i.prototype.setHRIRBufferList=function(A){if(!this._isBufferLoaded){for(let t=0;t0){const s=i(t,1);return o(A,1,t-1,e,n)*Math.sqrt(1+s)-o(A,-1,1-t,e,n)*(1-s)}{const s=i(t,-1);return o(A,1,t+1,e,n)*(1-s)+o(A,-1,-t-1,e,n)*Math.sqrt(1+s)}}function h(A,t,e,i){return 0===t?0:t>0?o(A,1,t+1,e,i)+o(A,-1,-t-1,e,i):o(A,1,t-1,e,i)-o(A,-1,1-t,e,i)}function c(A,t,e){const n=i(A,0),s=Math.abs(t)===e?1/(2*e*(2*e-1)):1/((e+t)*(e-t));return[Math.sqrt((e+A)*(e-A)*s),.5*(1-2*n)*Math.sqrt((1+n)*(e+Math.abs(A)-1)*(e+Math.abs(A))*s),-.5*(1-n)*Math.sqrt((e-Math.abs(A)-1)*(e-Math.abs(A)))*s]}function _(A,t){for(let e=-t;e<=t;e++)for(let i=-t;i<=t;i++){const s=c(e,i,t);Math.abs(s[0])>0&&(s[0]*=r(A,e,i,t)),Math.abs(s[1])>0&&(s[1]*=a(A,e,i,t)),Math.abs(s[2])>0&&(s[2]*=h(A,e,i,t)),n(A,t,e,i,s[0]+s[1]+s[2])}}function f(A){for(let t=2;t<=A.length;t++)_(A,t)}function l(A,t){this._context=A,this._ambisonicOrder=t;const e=(t+1)*(t+1);this._splitter=this._context.createChannelSplitter(e),this._merger=this._context.createChannelMerger(e),this._gainNodeMatrix=[];let i,n,s,o,r;for(let a=1;a<=t;a++){i=a*a,n=2*a+1,this._gainNodeMatrix[a-1]=[];for(let A=0;A ["+A+"])"),this._audioElementSource=this._context.createMediaElementSource(this._videoElement),this._foaRouter=new s(this._context,this._channelMap),this._foaRotator=new o(this._context),this._foaPhaseMatchedFilter=new r(this._context),this._audioElementSource.connect(this._foaRouter.input),this._foaRouter.output.connect(this._foaRotator.input),this._foaRotator.output.connect(this._foaPhaseMatchedFilter.input),this._foaVirtualSpeakers=[],this._bypass=this._context.createGain(),this._audioElementSource.connect(this._bypass);const e=Math.pow(10,this._postGainDB/20);c.log("Gain compensation: "+e+" ("+this._postGainDB+"dB)");const i=this;return new Promise(function(A,t){new n(i._context,i._speakerData,function(t){for(let A=0;A ["+A.toString()+"])."),this._config.channelMap=A.slice(),this._foaRouter.setChannelMap(this._config.channelMap))},i.prototype.setRotationMatrix3=function(A){this._isRendererReady&&this._foaRotator.setRotationMatrix3(A)},i.prototype.setRotationMatrix4=function(A){this._isRendererReady&&this._foaRotator.setRotationMatrix4(A)},i.prototype.setRotationMatrixFromCamera=function(A){this._isRendererReady&&(h.invertMatrix4(this._tempMatrix4,A.elements),this._foaRotator.setRotationMatrix4(this._tempMatrix4))},i.prototype.setRenderingMode=function(A){if(A!==this._config.renderingMode){switch(A){case c.AMBISONIC:this._foaConvolver.enable(),this._bypass.disconnect();break;case c.BYPASS:this._foaConvolver.disable(),this._bypass.connect(this.output);break;case c.OFF:this._foaConvolver.disable(),this._bypass.disconnect();break;default:return void h.log('FOARenderer: Rendering mode "'+A+'" is not supported.')}this._config.renderingMode=A,h.log("FOARenderer: Rendering mode changed. ("+A+")")}},A.exports=i},function(A,t){A.exports=["UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD+/wIA9v8QAPv/CwD+/wcA/v8MAP//AQD7/wEACAAEAPj/+v8YABAA7v/n//v/9P/M/8D//f34/R38EvzxAfEBtA2lDTcBJQFJ9T71FP0D/cD1tfVo/Wv9uPTO9PPmOufc/U/+agL3Aisc/RxuGKEZBv3j/iYMzQ2gAzsEQQUABiQFrASzA5cB2QmyCy0AtgR4AeYGtfgAA2j5OQHP+scArPsMBJgEggIEBtz6+QVq/pj/aPg8BPP3gQEi+jEAof0fA1v9+/7S+8IBjvwd/xD4IADL/Pf9zvs+/l3+wgB7/+L+7fzFADH9kf6A+n3+DP6+/TP9xP68/pn+w/26/i39YgA0/u790Pt9/kD+7v1s/Wb+8f4C/1P+pf/x/cT+6/3p/Xz9ff5F/0f9G/4r/6v/4P5L/sL+ff7c/pj+Ov7X/UT+9P5G/oz+6v6A/2D+9/6P/8r/bP7m/ij+C//e/tj/Gf4e/9v+FwDP/lz/sP7F/2H+rv/G/s7/Hf7y/4P+NAD9/k0AK/6w/zP/hACh/sX/gf44AOP+dgCm/iUAk/5qAOD+PwC+/jEAWP4CAAr/bQBw/vv/zf5iACD/OgCS/uD/Cv9oAAb/CgDK/kwA//5tACH/TgCg/h4AHP9aABP/JADP/hEAYv9gAAj/3f8m/ysAYv8gACX/8/8k/ysAXv8bABH//v8j/ygAa/8qAAD/9f9g/1YAWf8JACH/AgB2/z4AXP/w/z3/FgB2/ykAX//9/z//EwCV/zUAS//n/1T/GACK/x4ATv/0/4P/QQB4//v/WP/2/3X/HAB8//P/V//3/2f/AQBh/9v/Tf/x/5P/IwCI/wMAf/8hAKP/JACZ/xUAiv8nAK//HgCr/yMAm/8uAMz/OACi/yQAqf87AMT/MwCY/yUAtP9FAMH/KgCu/ycAyP85AMv/IwCz/xoA1f8qAMn/FgC8/xQA4/8nAMX/CwDJ/xQA4f8ZAMH/BgDO/xQA4f8WAMP/BwDU/xQA4P8QAMH/AQDb/xQA3P8JAMP/AgDh/xIA2v8EAMj/AgDk/w0A1f/+/8v/AwDm/wwA0v/+/9H/BgDl/wkAzv/8/9T/BwDk/wcAzv/8/9r/CQDi/wQAzf/8/9//CADf////0P/9/+L/BwDd//7/0////+T/BgDb//z/1f8AAOf/BQDZ//v/2v8CAOb/AwDY//v/3v8EAOb/AgDY//3/4f8FAOX/AQDZ//7/5P8GAOP/AADb/wAA5/8GAOH////d/wIA5/8FAOD////f/wMA6P8FAOD////h/wQA6P8EAN7////h/wUA4v8DANv/AQDd/wQA3P8CANn/AgDb/wMA2/8CANv/AgDd/wIA3v8CAOH/AQDj/wEA","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAAAAAAA/f8CAP//AQD//wEA//8BAP3/AAACAP7/+f8AAAIA/P8FAAQA8/8AABoA+f/V/wQAHQDO/xoAQQBO/ocA0Px1/ucHW/4UCm8HLO6kAjv8/fCRDdAAYfPiBIgFXveUCM0GBvh6/nz7rf0J/QcQSRVdBgoBSgFR62r9NP8m+LoEAvriBVAAiAPmABEGMf2l+SwBjva6/G4A//8P/CYDMgXm/R0CKAE6/fcBBwAtAND+kQA0A5UDhwFs/8IB8fydAEP/A/8v/e7/mP8j/2YBIwE3Av0AYv+uAOD8lgAg/wwAIf/L/n0Ae//OAJMB3P/XAF//XwCM/08AB/8NAEf/rf4jAT3/lgAJAP4AHgDpAO8AUf9L/07/Qf8KAOD/x/+D/3sATQCDAMoA0f79/+L/EQDt/7EAqv+S/7IAuv/o/wgAc//X//H/SwCm/+3/Yf/B/yoAAADI/7X/AwBg/5EATgCX/xYA/P+q/00AVACY/6v/BADD/zwALQCN/8z/KQDu/ygAEgCZ/6f/VQDC//T/KQCs/7P/UgAfAO7/NgC8/57/awAZAPP/+P/V/8z/bQBBAL//DgD0/+T/TABBAMz/CwAxAPz/SQBqALn/BgALAPz/EAA7AIz/3/8iAAUA//8kALf/y/9VABQA+v81AOj/0P9cAB4A+f8WAOr/vv83ABgAw/8JAOj/4f8nACIAsf/y/w4A3v8gACQAxP/n/ycA7P8WAC0Ayf/U/ycA9v/7/yUA0P/P/zUABADc/xUA5P/J/zcACwDS/xUA9P/m/zAACQDX/+3/9v/2/yQACgDZ/+P/AwAKABYA///b/9j/EQALABkADgD6/+7/GwD4/w4A8P/w//j/EgAEAAUA9f/1/wQAGgD4/wAA5////wAAGQD1////7f8FAAUAFQDv/wAA6v8LAAcAFQDs/wEA9P8SAAYACwDr//7/AQASAAYABQDv/wIAAwAWAAIAAgDv/wAABgATAAEA/f/u/wQABgAQAPr/+P/z/wUACQALAPj/9//4/wgABwAKAPT/+f/5/w4ABwAIAPT/+//9/w4AAwADAPH//f///w8A//8BAPP///8BAA0A/f/+//X/AgACAA0A+//8//b/BAADAAoA+f/7//n/BgADAAcA+P/7//v/BwABAAQA+P/8//3/CQABAAIA9//9////CQD/////+P///wAACAD9//7/+f8AAAAABwD8//3/+v8CAAAABgD7//z//P8EAAAABAD6//3//P8FAP//AgD6//7//v8FAP7/AQD7//////8GAP7/AAD7/wEA//8EAP3/AAD9/wEA/v8DAP3/AAD9/wIA/v8CAP3/AQD9/wIA/v8CAP7/AQD+/wEA"]},function(A,t,e){"use strict";function i(A,t){this._context=h.isAudioContext(A)?A:h.throw("HOARenderer: Invalid BaseAudioContext."),this._config={ambisonicOrder:3,renderingMode:c.AMBISONIC},t&&t.ambisonicOrder&&(_.includes(t.ambisonicOrder)?this._config.ambisonicOrder=t.ambisonicOrder:h.log("HOARenderer: Invalid ambisonic order. (got "+t.ambisonicOrder+") Fallbacks to 3rd-order ambisonic.")),this._config.numberOfChannels=(this._config.ambisonicOrder+1)*(this._config.ambisonicOrder+1),this._config.numberOfStereoChannels=Math.ceil(this._config.numberOfChannels/2),t&&t.hrirPathList&&(Array.isArray(t.hrirPathList)&&t.hrirPathList.length===this._config.numberOfStereoChannels?this._config.pathList=t.hrirPathList:h.throw("HOARenderer: Invalid HRIR URLs. It must be an array with "+this._config.numberOfStereoChannels+" URLs to HRIR files. (got "+t.hrirPathList+")")),t&&t.renderingMode&&(Object.values(c).includes(t.renderingMode)?this._config.renderingMode=t.renderingMode:h.log("HOARenderer: Invalid rendering mode. (got "+t.renderingMode+') Fallbacks to "ambisonic".')),this._buildAudioGraph(),this._isRendererReady=!1}const n=e(1),s=e(8),o=e(9),r=e(17),a=e(18),h=e(0),c={AMBISONIC:"ambisonic",BYPASS:"bypass",OFF:"off"},_=[2,3];i.prototype._buildAudioGraph=function(){this.input=this._context.createGain(),this.output=this._context.createGain(),this._bypass=this._context.createGain(),this._hoaRotator=new o(this._context,this._config.ambisonicOrder),this._hoaConvolver=new s(this._context,this._config.ambisonicOrder),this.input.connect(this._hoaRotator.input),this.input.connect(this._bypass),this._hoaRotator.output.connect(this._hoaConvolver.input),this._hoaConvolver.output.connect(this.output),this.input.channelCount=this._config.numberOfChannels,this.input.channelCountMode="explicit",this.input.channelInterpretation="discrete"},i.prototype._initializeCallback=function(A,t){let e;(e=this._config.pathList?new n(this._context,this._config.pathList,{dataType:"url"}):2===this._config.ambisonicOrder?new n(this._context,a):new n(this._context,r)).load().then(function(t){this._hoaConvolver.setHRIRBufferList(t),this.setRenderingMode(this._config.renderingMode),this._isRendererReady=!0,h.log("HOARenderer: HRIRs loaded successfully. Ready."),A()}.bind(this),function(){const A="HOARenderer: HRIR loading/decoding failed.";h.throw(A),t(A)})},i.prototype.initialize=function(){return h.log("HOARenderer: Initializing... (mode: "+this._config.renderingMode+", ambisonic order: "+this._config.ambisonicOrder+")"),new Promise(this._initializeCallback.bind(this),function(A){h.throw("HOARenderer: Initialization failed. ("+A+")")})},i.prototype.setRotationMatrix3=function(A){this._isRendererReady&&this._hoaRotator.setRotationMatrix3(A)},i.prototype.setRotationMatrix4=function(A){this._isRendererReady&&this._hoaRotator.setRotationMatrix4(A)},i.prototype.setRenderingMode=function(A){if(A!==this._config.renderingMode){switch(A){case c.AMBISONIC:this._hoaConvolver.enable(),this._bypass.disconnect();break;case c.BYPASS:this._hoaConvolver.disable(),this._bypass.connect(this.output);break;case c.OFF:this._hoaConvolver.disable(),this._bypass.disconnect();break;default:return void h.log('HOARenderer: Rendering mode "'+A+'" is not supported.')}this._config.renderingMode=A,h.log("HOARenderer: Rendering mode changed. ("+A+")")}},A.exports=i},function(A,t){A.exports=["UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD+/wQA8/8YAP3/CgACAAAA//8CAAYA8/8AAPH/CgDv/97/e/+y/9P+UQDwAHUBEwV7/pP8P/y09bsDwAfNBGYIFf/Y+736+fP890Hv8AGcC3T/vwYy+S70AAICA3AD4AagBw0R4w3ZEAcN8RVYAV8Q8P2z+kECHwdK/jIG0QNKAYUElf8IClj7BgjX+/f8j/l3/5f/6fkK+xz8FP0v/nj/Mf/n/FcBPfvH/1H3+gBP/Hf8cfiCAR/54QBh+UQAcvkzAWL8TP13+iD/V/73+wv9Kv+Y/hv+xPz7/UL83//a/z/9AP6R/5L+jf26/P3+rP26/tD8nP7B/Pv+WP1V/sP9gv91/3P9xP3J/nv/GP5S/sb+IP8v/9j/dv7U/pr+6v+u/Z3/sv5cAOr9Q/83/+n/zP5x/57+2//k/nwA/v01//L+SACB/sD/Ff81AJT+TgDp/ocAm/5dAFT+MgD+/pMAW/7o/yH/xQDA/kkA9P6LAL3+pAC0/iQAz/5UALD+UwAt/3UAhf4UAA//pwC+/joAz/5aAAv/fwDY/iMAIf+uAPP+ZAAc/0QAy/4xAB7/TgDs/goADP8wAEL/NwDo/ub/Uf9BAC3/+v9F/y4ARP9HAFP/EQA3/xMATP81AG3/HQAu/wgAaP9FACb/9f9B/y0AUP8rAED/CwBV/z4AW/8TAGH/BQBK/xsAfv8eAFn/AgB3/zwAff8RAGj//v+E/yAAb//0/3n/FwBz/xcAiv8PAHn/FQCJ/xgAg//x/3j/EQCa/ycAff/w/47/HwCI//X/iv/7/43/JQCM/+n/kP8AAJb/JACj//7/oP8ZAML/SwCo/w4Atv8tAMb/PACr/xcAwP9HAMP/OADF/y4A0f9IANL/NwC//zEA0f9LAMb/MAC8/y4A3f9GAMH/FQDQ/yYA2/8sAMT/AwDX/xkA3v8SAM3/9v/c/w8A4f8LAMj/8f/h/xQA2P8CAMn/8//j/xQA0v/7/9H//P/i/xEA0v/1/9L//f/j/w0A0f/x/9f//v/k/wgAz//u/9z/AwDg/wMA0P/v/9//BQDf////0v/y/+D/CADc//3/0v/2/+L/CgDa//r/1v/5/+T/CgDY//j/2f/9/+T/CADY//f/3P8AAOT/BwDY//f/4P8EAOP/BADZ//j/4v8GAOL/AwDa//r/5f8IAOH/AQDc//3/5v8JAOD//v/f////5v8IAOD//v/h/wIA5/8HAOD//f/j/wMA5/8GAOD//f/l/wYA5v8EAOD//v/m/wYA5f8CAOL////n/wYA5P8BAOH/AADl/wUA4f///+H/AQDk/wMA4f///+T/AQDm/wEA5////+r/AADt/wAA7/////P/AAD1////","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD//////v///wAAAAAAAAAAAQAAAAAA///9/wAABAD+//n/AgAJAAAA+v/+//f/DAAdAPv/+v+l/8L+jf/4/vgAdwVPAQACLQBo+Qj/Ev7o/N3/VgCbA08Bxf+L+yn9J/2HCU8FmgBvDe30Rv5h/LT09gi5CxkA5gOi8/30kwEM+4YJMf2nBmkJJAQQBLoFtvvv+m4A7PF6/R0Bif3qAuf8WARAAf4GyABG/BIAwvr4Acv8U//c/yIC8AEn/B8Daf2CAgMBAf3MAN38vgLK/UT/QwCyAPYClPyvAW/+pQAoASD+zP+R/IYC1f7C/nEBQP96AZb+1QAIAM//yQE7/tkAZ/7TAXL/w/8+AIsAtwB7/24A4v9a/z4A7v4iADb/dwCj/23/kgBOANUAIv8lAKEAxP9gAK7/BwCP/5kA7/9v/0wAzv9DAGT/3/9vAHv/6P+q/xUA7P8XAO//uv/g/2UAEgCV/wEATADM/+7/+//j/+D/9v/i//j/IgD+/xoAxf/6/z4A5/+8/9D/QwDq/+3/OQDT/zUAIgA/APP/PgAjAPD/BwAGACAADAC3//b/HAA3AN//RgDN/w8AIAACAN//GQBDACEAIwA+ACoAJQAeAPz/KgAYAPr/DgAEABYAIgAcAMT/7f8OAOL/5P/2//L/9P8GAPT/7v/8/+7/6v/t//z/AgAUAOL//P8VAAMA4/8IAPb/+P8MAAoA5v8NAAsA9v///wEAAAD9//n/9/8JAAYA7v/6/wMA+f8GAAEA7f/7/xgACAD4/w8A///3/w0A+f8BAAIA/P/5/xIA///9//r/7v/+/xYACQD///H/CwDz/wEADgAHAPP/FADn/+3/AQD5//f/AgD7/wEABwAMAAEADQD8//n/8f8OAPX/BAD+//X/+v8WAAQA+f8CAAEA7/8QAAEA/P8DAAUA9f8KAAwA9v8DAAUA+f8OAAoA9f/7/w0A+v8EAAgA8P/6/woA+//8/wkA+P/3/woA+//8/wcA9//1/woAAwD5/wcA/P/3/w0AAwD3/wEABAD2/wkABgD3/wEABQD3/wUABQD3//v/BwD3/wMABQD3//r/CQD7////BQD6//n/CQD9//3/BAD9//j/BwAAAPv/AwD///j/BwABAPn/AQABAPn/BQACAPn///8DAPr/AwADAPr//v8EAPv/AQADAPv//P8FAP3///8DAPz/+/8FAP7//f8CAP7/+/8EAP///P8BAP//+/8DAAEA+/8AAAEA+/8CAAIA+////wIA/f8AAAIA/P/+/wIA/f8AAAIA/f/9/wMA/////wEA///+/wIA/////wAAAAD+/wAAAAD/////AAD//wAA//8AAP//AAD//wAA","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD////////+//////8AAP////8AAP//AAAAAPz//f8IAAMA9////w4AAQD6/wwA8//+/y8Afv/0/2H/UP5gAbH+2QG1B2cAVAIh/l32FPyM/nACPQDV/+UEo/Q6AQwCu/oLD9kF8QJA/Uz+Wf2KCOcC+wUKBsL5aQBQ97rwOPiPAvn5CAl8AHEDkQPcAA8Bn/lIAdz7HQF1+xz9cAM4/94E4gDKAun+cgPYAYr9JgJr/bf+ivxz/MoBgv5UA8EBSgAQAJ7/UgEk/cQB7f63/sD/vf4XAhT/BQFCADYAnQGI/9EBtv3hALD/vP+c/3H/TgIN/1sBpf8yAP3/4f8qABr+1f8OAJ3/dwAGADEBnv9JAPz/IQBwAIH/jgAS/4wAsACTAOn/DQDCALn/ZQCSAAIAAwD1/9//jv9aADQA/v9EAB0AfgA8AAQACgB9APr/IAARAPT/5v9xACAABAAHAGUAt/89AC4ACgAjAMP/+v/9/xYA7f/1/+D/7P87AC0Auv8RAAcA9/8FAC8A2//y/xIAEwAaADQAJADp/zoAAgAfABIA2f/e/zUA+P/6/w4A9//A/zcA4//P//T/5f/R////EwDb/w4A8/8BABkANADh/xEA+f/0/wIAHADc//j/GwD1//f/GADs/+v/EAAAAPz/EgD3/+r/FgAMAAkAGAD9/+z/IQAQAPH/GQD3//z/CgAfAOX/AgD8//H/BAATAOv/+v///wIABAAdAOj/BQAPAAcAAQATAOz/8/8JAAkA6f8VAOv/+f8QABUA/v8OAO3/+P8KABUA9f8FAPv/5/8TAA0A7f8XAAkAAQAJABYA4/8WAAcACgANABEA7v8EAP7/AAD+/wMA9//7/xAAAQD8/wQA+f/7/wMABgDq/wAA+v/3/wYACQD1//3/BAD9/wgADgDw//r/AgD6/wEACADv//j/BQD///X/BwDu//j/AgACAPP/BAD2//n/BAAGAPb/BAD8//3/BQAJAPL/AwD+//3/BAAIAPP//f8DAPz/AAAGAPP/+/8CAP7//f8FAPX/+f8DAAAA/P8EAPf/+v8GAAMA+/8EAPv/+/8GAAQA+v8CAP///P8EAAUA+f8AAP///f8CAAUA+P///wEA/v8BAAUA+f/+/wIAAAD//wUA+v/9/wMAAQD9/wQA+//9/wMAAgD8/wMA/P/9/wMAAwD7/wEA/v/+/wIAAwD6/wEA///+/wAABAD6/wAAAQD//wAAAwD7////AQAAAP//AwD8//7/AgABAP3/AgD9//7/AQABAP3/AQD+//7/AAACAPz/AAD+//////8BAP3/AAD//wAA//8BAP7/AAD//wAA/v8AAP7/AAD//wAA//8AAP//","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD//////P/9//3//////wAAAAAAAAIAAgACAP//CAAEAEEA//+cAAUAb/8HAAH9+P9eARkAogQUAJn8BwCd/gX/+QQNAKoC9gFdAtb/b/vd/936TP/6AsD/nfqn/un1W/0dA8IEsQLvAJv2bP72+WMAkP8dAcX+nQO2AIr6bP/EABX+NgK/Bdj2IQv2AE4EUAiD/xQAnwIm/B0B/wGNAoH7sQaP/b8CiQakAqD+R/9xA477KQL//6r75v/O/pcCgQCtAiMCBQAkANAARwHf//39hgBl/kUAJgEtAUEATgA/AgoASADK/zUAJv29/vL+l/9c/0cAUwBBAE8A6QE5/87/Wv9NAOf+5v7P/5P/4/9BAKYAQwDD/zYB5v+r/zYATwAp/1v/WQAEAB0AhwA0AA0AIAA3AAEAzv/u/+//5v9m/zwAIADQ/8T/SABiANb/SwAbAFf/MQDX/7L/hP8TAPr/AgAMAAsAHwAZAI3/VgDC/9v/5//x/6P/AwBlAMv/yf82AB4A+P9WAPj/NwDi/1EA0v9JANj/JwAcAAEADABYANj/4f8MAEwAmP82AN//3P8UADYA7//6/wIACADU/ygAyv82AN7/9v/2/ygAxv/9/+3/5//n/zUA6//g/y4ADgD5/wsABwDv/xIADwAGACoAJQD3/zIA+/8FABsAFgDO/zAAHAAIABQALADp/xcACAAAAPH/GADs/wkACQAFAAgAFQDp/wIAHAD1//P/EQDw/+3/GAD9/+f/HAD8//T/DAAQAPH/HwD4//r/DwAPAOj/EQACAOn/DAAXAOX/BAAOANH/9/8MAO//9f8LANT/9f8EAO//6f8NANb/+P8KAOz/5v8MAOD/7f8UAO//7//+//7/9v8YAPj/9f/z/wsA+v8SAPD/+v/x/xYA+f8SAPb/9//3/xEABQACAPn/9//y/xQACQD///b//v/7/xIACQD9//H/AAD7/xEAAgD5//P/AwD9/w8AAgD3//D/BAD//wUA/v/0//D/BgADAAMA/P/2//f/BwAGAP7/+//2//j/CAAFAPv/+f/5//v/BwAHAPn/9//7//7/BQAFAPf/9//+/wEABAACAPf/+P8BAAIAAgAAAPj/9/8CAAMAAAD+//n/+f8EAAQA/v/8//r/+/8EAAMA/P/7//z//P8EAAIA/P/5//7//v8DAAEA+//5//////8CAAAA+//5/wEAAAABAP//+//6/wIAAQD///3//P/7/wMAAQD///3//f/9/wIAAQD9//3//v/9/wMAAQD9//z/AAD//wEAAAD9//z/AAAAAAAA///9//3/AAD//wAA/v////7/AAD//wAA////////AAD//wAA//8AAP//","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD+////+f////v//v///wAA/////wUAAQAIAAIABwACAHkATAAOAaMAAf9C/9X6QvwhArAAtghABW37nv/y+0wAWQNcAE8JRwSOC6AEJe8P8S/zrPWaBI/+LQA/+0L+P/4K8AgAb/8uCh78BQtC614GaQWfAin5UfzN8Tf+GQizAZ4MCQMbGJ4BoRS7AvcHyQARA6n9ZwHZ/z4DvwAZAlAB6gbNAS4GFADFATL7E/2K+j37C/xp/SD9Uv0VAOsDs//WAd3+bv7F/f79mP2X/KH+FwC0/1n+VgFcATABHQGaAET+nf8Y/hoAovpqAXj9CQKW/lsCl/4RApj+bAHk/RcAlv4BAG/+DgDi//3/GwAOAEIAq/+y/3z/8v8+/7T/Tv8//27/mgDZ/1sA+P+cAAAA/P/i/yMAi/85AMP/KgDM/9MA9P+QABoA4QAiACwACwBdAP7/TQDb/y0Ayf+SAA0AZwDg/4wA+/8/AAMAgQDp/w0ADAAQAAoANgAgAA4AKABIAB4A4v/3/+f/+v/c/+n/EADn/wgAFAAqAOz/IwDc/9//3f8XAND/2v/a/w0A5v8BANb/9P/m/wAA8P8ZAN3/RwAGAEsABgB/AP7/NAASAEgABAA3AP3/KgD9/1sA8P8lAOr/FgD1/xAA4/8kAOv/AwD4/xEA5f8NAPT/+v/3/x8A7f8PAPj/IwD5/yAA9/8ZAAEAGgD4/xoA9f8HAAMACAD0/xgA+P8AAPr/IQDp/w4A8v8HAPX/IgD1/wYA+P8GAPX/GgD3/woABQASAAcAGQDw/+v/9P8bAP3/HADs/+f/7/8LAPr//v/0//T/AgD2/wsA6P///+P/CADY//7/5v/3/wQA/v8LAPD/GgD1/yMA/P8QAOv/LADw/yQA+P8XAO7/MQD9/yEAAQAcAPD/IgD9/xMA+/8OAO//FQABAAoA+/8PAPP/FQABAAQA9/8PAPX/CAADAAEA+P8NAPv/CAAGAAUA9/8JAP//AAAFAPz/+f8HAAQA/f8FAP3//P8FAAYA+P8DAP7/+/8AAAcA9/8BAP///f///wgA9//+/wAA/v/8/wUA9//8/wIA///7/wUA+v/7/wIAAAD6/wMA/P/6/wEAAQD6/wEA/v/7/wIAAgD6////AAD7/wEAAgD7//7/AQD8/wAAAwD8//3/AwD9/wAAAgD9//z/AwD/////AgD+//z/AwAAAP7/AQD///3/AgABAP3/AAAAAP3/AgACAPz///8BAP3/AQACAP3//v8BAP7/AAABAP3//v8CAP7///8BAP7//f8CAP////8AAAAA/v8CAAAAAAAAAAAA/v8BAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAAAAP//AAD//wAA//8AAAAA/////wAAAQD+////AAAGAP3/OAABAIIAAwBv//f/E/0QAK0ADQCzA/7/8P4u/0cBDQCJA6ABbQDg/w7/z/9o+Vn/SPnL/1//Ef+2+jr9RfZgA5QFZwILDFj+PAb2/nEFKgKk/R0Dlv6b/FUDsP6YAoj9SgAT/iL/tAPwAv8A0P6zAr7/dwAnAf39uP22/skA2v///2YCoP4UAUsAZgF2AJH+4P70/rz9+f+U/Xv/8v7CAcb+TACS/kwAv/+x/tX9oP71/oL/1f8nAEUAZwGtAAgAIgC/AD4BaP8GAGH/dQDF/64Arf8nAakAhAH9/+kAQQD3AFb/q/8p/yIAR/8FAPD/ZAA/AIYA3v8tADQADQBp/3f/CwABAP3/Wf8OANj/WwDH/xoAe/8DAKz/zv96/z8A3f/J/5X/IAD5//j/q//c/+//RADq//D/vv8pADUAFQDI/y8ACAAbANb/OwD3/+3/9f/e/wcAIAAeAMH/8/8xAC0AEADW/+3/HAADAPv/8P8DAOL/OwD3/xcACQAHAM//5f8XAAcAz//T/9D/HgD9////yf/e//v/AgD//9H/6/////H/+/8hAAIA9//7/w0AFgAQAPL/2v/8/xsAGQABANz/9P8YAAQA/v/y/wMA5v8YAAkAAAAAAAMA7/8KABgADwDs//j/BwATABsA8P/1//z/BAAMAAAA9P/s/xAA/v8GAAkA/v/p/wMACwALAP7/9P/p/wcADQAFAPb/7//4/w0ACAD8//b//v/1/wMACwD1//T/8P/8/wAACQDz/+f/5P8GAAkABQD5//D/+v8FAA0AAwD///T/AgACABAA/v8CAPD/+/8FAAoA9f/3//f//v8GAP7/9v/t//z/+f8AAPj/+v/3/wEA+v8HAPr//P/5/wQA//8DAPr/+P/3/wYA///+//X/+//5/wQA/f/7//X/+//4/wMA/f/8//j//v/9/wYA///8//f/AgAAAAUA/f/6//n/AwACAAIA/f/7//z/AwACAAAA/f/6//3/AgADAP7//f/7/wAAAwAFAPz////8/wMAAgAEAPv//v/+/wMAAgADAPv//v///wMAAQABAPv//f8AAAIAAAD///v//f8BAAIA///+//z//v8CAAIA/v/9//3///8CAAEA/v/9//7/AAACAAAA/v/9////AAABAAAA/f/9/wAAAQABAP///f/+/wEAAQAAAP///v/+/wEAAQD///7//v///wEAAQD///7//v///wEAAAD+//7///8AAAAAAAD+//7///8AAAAA///+//7///8AAAAA////////AAAAAP////////////8AAP//////////","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAAAAAAAAAABAAAAAAD//////////////v////3/////////+//8////AQD9//z/9f8BAAIA+f8dACgAWQBxAJX/qv+Y/uz9aP9k/7UDUQQBAiQA4Pgi/AkB0gKaBsD/+fxp/vz9CQSp/I/+ywDO+vMD0fzK/PABcgBeBfoBv/+uAuH9Sf5gAy39awMmBWUBuP9fA9/9fgDj/2/+EACaACcCSv9Z/2j/rv7hAA0AWf55/7L84P7E/SIAT/67AMv/tf+FAA7/1v+7/gv/IP+E/sQA+P5aAXz/tP9XAFX/tP8o/4r/j//e/yQAMv9mAJT/rgCr/9X/EwCb//H/9f7F/6D/EAAoAK3//v+e/zsAh/+B/7r/if/C/2r/4P/z/6//HwCy/0IA7/9ZALT/y/80ACgA9v/J/9//DgA5ADUALQARADIACwAfAOf/NgArACMACQBBAEcAGAAjAC4AWQBUAHcAAAAfACEAIAAcAPj/CADk/yQA7v89AEEAFwD5/xYA6f8aAOX/AADF/zQADwAUAOT/BQDr/yUA6P8XAOf/HADR/0AA8P8nAAgACQDt/ycAKAAHAPH/IQDz/xsACADn//n/DgADAA4A8P///8z/GgDN/yMA/f8QANj/MwACAC0ACwAOAO3/JgAZAAUACgAAAA4AIgAaAAkADwACAAAAHQATAAUABQACAAgACwAjAO////8AAA8ABQAPAPL//f8GAAsABgAGAPD/8v8GAPz/CAD6//H/6v8PAAgABgD4//3/9v8aAAgABwD1//7//v8QAAoACAD//wUA9v8QAAoABAAFAAgAAgAJAAoAAwD//w0AAgD//wcA/v8DAAoABQAFABUABAAKAAYABwAHAA8ACgAGAAwADwAMAAkAEAAJAAgADwAMAAgADgAJAAUACQAPAAUACwAHAAEABgAIAAEABAAGAP//AgAJAAAAAgAEAP7///8IAAIA//8GAAEAAQAJAAIA/v8EAAMA//8JAAEA/v8DAAMA/v8HAAMA/f8BAAUA/v8FAAMA/v8BAAcA//8DAAMA/v8BAAYA//8CAAMA/////wcAAAAAAAMAAAD//wYAAQD+/wMAAQD//wUAAQD+/wIAAgD//wQAAgD+/wEAAwD//wMAAwD+/wEAAwD//wIAAwD//wEABAAAAAEABAD//wAABAABAAAAAwAAAAAABAABAP//AwABAAAAAwACAP//AgACAAAAAwACAP//AgACAAAAAgACAAAAAQADAAAAAQACAAAAAQADAAAAAQACAAAAAAACAAEAAAACAAEAAAACAAEAAAABAAEAAAABAAEAAAABAAEAAAABAAEAAAABAAEAAAABAAEAAAAAAAAAAAAAAAAA","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAAAAP//AAD//wAA//8AAAAA//8AAP//AAACAAAA+f8BAAYA///4/wIA//8AAA8A/v/V/wEAEwA9AAEBRwA2AF7/kfog/3gBwv99CDYBU/qtAUX/AP7OAfkAX/o9B38FSfwaAuT14/60BAr8CQAI/tfyIQTzAXP+egdUBBwBof7TBMT8bAWi/5EEWwBRAAAKyfxE/8b88vp6ACP+PAF4/qD8MQNM/ygCJ/2XAPD9kP5gAVT/iP9I/lEB4P8qAD0BFAGa/+7/DgB2AOP98gFm/u/+Vv5/AG8ASP9gAM//qv9w//oAcv+2/jIBHgA7/6D/oAAGAKH/lADT/wAAggC8AAYAkP9yAEcAkf8BAOD/RAAr/zUANwDt/xQAJQAkAMT/zwA/AOH/xv9zAGsANQBTAIcALAAvACIATACy/xMADADg/xcAWABvAJL/7f9VAPb/EgDt/wcA4f8kAPP/5P+h/wgACQDy//r/LgAQAMn/8/9CAOX/5v/S/9//3P8pABYAuP/s/w8AFgDt/+3/7v/w/9j/5/8GAOf/2P/2//P//v8kABMAuf/m/xoADADZ/+r/3P8KAAUAKwDe/wsA3P8VAAAADgAfAB0ACAAMAF4AGgAhAPL/MwDz/0kABAAKAPX/LwAbAAkA9v/s/+3/8/8CABAAAADm//n/BQALAAUAAQDj//n/JQAVAPX/9v/+/wIAEQABAPP/8P/1/wAABgD6/+3/7//o//j/DAD8/+b/8P8IAAkABgD4//D/8P8UAAoAAwD4/wAA+f8OAAcAAAAFAPX/9v8TAAkA8v8EAPb/9/8dAA0A7/8CAPn/+f8SAAQA8/8CAOf/+v8DAAgA9P////H//P8IAAUA8//0/wIAAQAGAAgA9//7/wAA+/8EAP//+P/+////AgACAAsA8v/+/wIABQD7/wgA9v/7/wMABAD5/wAA/P/3/wEAAQD7//7//P/1/wQA///3//r////3/wMAAwD1//r/AwD6////AgD4//n/AwD8//7/AgD4//n/AwD+//3/AQD4//n/BQD///n/AAD6//j/BAABAPj/AAD9//v/AwADAPj//v/+//z/AwAEAPj//v8BAP7/AQADAPj//f8CAP////8EAPr//P8DAAAA/v8CAPv//P8DAAEA/f8BAP3//f8DAAIA/P8AAP7//f8DAAIA/P///wAA/f8BAAIA+//+/wEA//8AAAEA+//+/wEA/////wEA/P/+/wEA///+/wAA/f/9/wEAAAD9/wAA/f/+/wEAAQD8/////v/+/wAAAQD8////////////AQD9////AAD/////AAD+////AAAAAP//AAD///////8AAP//AAD//wAA//8AAP//"]},function(A,t){A.exports=["UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD+/wQA8/8ZAPr/DAD+/wMA/v8KAAQA/f8DAAMABADs//z/8v/z/8f/R/90/ob+//zAAWsDAwY3DKn9//tu93DvkwI6An4CuwJ0/BH7VPux92X0Gu7N/EX9mgfqCkkIiRMgBd4NQQGL/c0G/xBxAKELZATUA/sIHRSx+fkCyAUmBNEJIARlAdHz2AjNACcIsAW4AlECsvtJ/P/7K/tf++n8aP4W+g0FXAElAMn8nQHn/sT+Zv7N+9X2xvzM/O3+EvpqBBD7SQLd+vb/sPlw/JD72/3n+Rr+L/wS/vz6UQGg/Nf+Av5L/5X9Gv2//SP+mf3j/lf+v/2B/ZH/5P05/iL9MP9F/uf9UP4v/qv9mv7o/Xn+wP2k/8L+uP5J/tD+Dv/Y/bL+mP72/n3+pP+7/hAA+/5zAGH+Z/+u/g8Azv2y/6L+//9o/iIADP8VACz/CwCN/pb/1v4yAFP+wf+4/jsAcf5VAP3+bADa/nMA6f4sAOT+IQBd/v7/7v6aAIL+QADe/nEA0P4yAKz+CQCo/moAuf5xAN7+mAC8/jcANf9eAPX+IAA1/1kAAP9hAMz+PQD5/m0A2/4gAPr+UQDh/jQAEv9BAPH+FABN/zkASv9DADP/BABe/1IAGf8oAE3/RQAw/zIAQf8mADn/GgBE/xIAR/8hAD7/BABy/zEAKP/0/07/GwBX/z4ARf8mAFr/QQBV/zUAVP8eAFz/JABt/0EAUP8MAHz/KgBr/ycAYv8EAH3/MABl/x8Agv8bAIj/GgBv//z/ff8AAJX/IABu/+T/jv/r/4z/9/9n/77/pP8JAJD/EQCJ//r/q/8WAJ//GQCU/xYAtv8qAKr/PQCW/ysAwf8+ALb/OgC3/ygAz/8uAM7/OgDH/ygAz/8kAMz/OgC//xsA1f8qAMn/LwDN/xcA1f8oAMv/JQDR/xMAzf8bAM//HgDU/wUA2v8ZANL/EwDW/wEA1f8ZAMz/BwDX/wIA0v8SANT/BQDW/wMA0/8PANT/AADY/wIA1f8MANX/+f/a/wUA0v8IANf/+//Y/wUA0/8DANr/+f/Y/wQA1v8BANr/+f/Z/wUA1//8/9z/+v/Y/wYA2f/8/93//v/Y/wUA2v/9/93////Z/wUA3P/8/97/AgDa/wMA3v/8/97/AwDb/wIA3//9/97/BADd/wEA4f///9//BQDf/wAA4v8AAN//BQDf/wAA4/8CAN//BADh/wAA4/8DAOD/BADi////4/8DAOH/AwDk/wAA5P8FAOL/AgDl/wEA5P8FAOL/AQDl/wEA4/8EAOL/AQDj/wIA4P8DAN//AADg/wIA3v8CAOD/AADh/wEA4v8AAOP/AADm/wAA6P8AAOz/AADu/wAA","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD//////f/+//7///8AAP////8BAAEA/f8AAAEAAQAFAAUA9//6/x0A2f/9/xMA3P+jAE//of9HAKP//gCj/77/Z/vi/28D9/ywDJAJIvr6AsX0Xec4BhcGzf23DZP7yfZ6C1//nwBDBIHyYgob/Tf3sQ41ANoKRA/A+E7yffAa9gD5EQUBDMwMygiqAHMAqPqhAGUB2/gE+a78H/+4APT6DwIUAA0HNwMhBfL8E/90A5n7dP9cALIC+v5C/q0AOv9kAogBHv01/+3/qAQD/ub8T/4vAOUA5P6KATv+ywEYAeT+KP6i/3gCFP6h/hr/+P83ACL/VADn/8UARQJI/4MAu/8qAlj+wf4iAPb/LgFJ/8QAUABAAI4ABf+k/3X/YgFK/ij/j/9HADoAi/+WAA0BVwC/ACL/LACe//cARv9i/xgAUgA0ACj/FgBgAIj/5P9M/7z/zv8/AKz/gv8sAEQA6/+I/yYAawDL/7T/xf8qAOv/FQCu/5n/EgAyAO3/i/9LAE4A+//R//P/FgDe/8z/u/8DADIALAAZALL/TAA8ABwAo//1/xwA/P/L/z0A6P8jAN7/7v+a/zAAwf/7/3//KQAuACwA9v8RAGYAIwBNADgAKgASAF0ADgANACEAMQDH//H/LQACAB0Ay////x0APAABAAQA2v8iAAcAEgDE/+v/FQD+/+P/DAD1/97/6v/4//X/EwD4/+7/5P8cAA0ACQDH//7/CQAXAAEA/P/5//j/CwAWAAEABQD9//n/AQAWAB0A7v/k/wAACQAmAP//9/8AAPn/8/8aAO//6/8fAOv/5v8hAP//5/8PAOf/AAAGAPn/6v8JAAYABgABAOv/1//1//L/+P8DABcA6f/8/wMACgD7/xAA3v/2//z/DADu//z/5v/5/wEA/P/6//7/7v/x/wQABgD5/wAA8v/w/wkAEQD2//j/+v8EAAcAEAD3//v/+v8CAAAACQD3//v//v/9/wUADAD2//X/AgAHAAAABwD2//T/BgAKAP7/AQD4//r/BAAIAPn/AAD3//f/BQAHAPv//v/7//n/BQAJAPj/+v/9//7/AgAGAPj/+f8BAAEAAgAFAPn/+v8BAAIAAAAEAPn/+f8CAAQA/v8BAPr/+v8CAAQA/P////v//P8CAAQA+//+//3//f8CAAUA+v/9//////8AAAQA+v/8////AAD//wIA+//8/wAAAQD+/wEA+//8/wAAAgD9/////P/9/wEAAgD8//7//f/9/wAAAgD8//3//v/+////AQD8//z/////////AAD8//3///8AAP7/AAD9//7///8AAP7////+//////8AAP7////+////////////////////","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD//////v8AAP///////wAAAAAAAP7/AQABAAAABwD///X/BQAjAPL/CQDb/9D/GAAb/7sAYwCW/z0BcP/X/7T/2QDW+wH8yANCCCUJ5QT++UXmhPwhA78FuAxH+p78ifudBlAG9vmu/lAK2fdlB///cfjoCa0E7Akn9Yb/zvba+AkAHPywBGEBFwUNAL8AXAAGA20DFvmR/kz+F/06Ag/+GwHl/5EEKgJd/q0AP/ym/9n6EfxY/2H+/QFtAC4C6QBDAaMCo/20/+3/3f/p/fL9rv9V/6cBhQHuAX4AcwJYAaH/IP/P/gsApP0LAe7/sQBuAI0AAgGDAE4BzACe/5X//v+v/+f+Zf+gAOv/5QBhAOIApAANASYAuP+h/8b/HQBr/9//bACWAGEAFAB5AD0AWQDU/+D/Yf/p//D/s/+R/4QAMQBvABEAkQBfABQAJgDW/wwA8/8XALz/vf8zAFAAKwD1/zEAPwDJ/x0A7/8LAOX/FwDR//H/EQAdAO//6P8QAFEA2f8WABEAMgDy/xIA+f/s/xAALgDv////HQAvAPT/+f8iAAYAEgAFABoAGgD//w0A+f/0/xsAHgDx/9f/GAACAPH/8f8JAPf/GwALABEA7/8cAPT/CgD2//j/BQD8/+3/OgAgAAYA9f8PAN7/DgD9/9r/1//3/+3/9//1//b/8//5//f/AgAJAOf/+v8OAAMACwD9/+7/5f8eAAEA9//q//7/8P8WAP7/+//4/wIA+f8TAAIA9f/5/wcA+P8iAAgA9v/n/xoA//8gAAUABwDj/wAA9v8BAAUAFQDn/wMA7v8QABAAEQDm/wwA8f8aAAAABwDu/wcACgASAAEA7//w//f/BgARAAkA6P/3/wcADgAKAAYA4f/4/wYADgAAAPr/8P/9/xQACgAHAPn/7//9/xEAAgD+//L/8v/8/xUAAwDw//H/9f8CAAsA/v/q//L/+f8FAAYA/P/r//j///8GAAkA+//o//j/AQAIAP//+v/o//v/CAAIAPv/+P/w/wEACQAHAPj/+f/0/wIACwAFAPb/+f/4/wQACwACAPP/+f/+/wYACAD///L/+/8BAAYABQD9//P//P8FAAUAAgD7//T//f8HAAQA///7//f///8IAAMA/P/6//r/AQAIAAEA+v/6//3/AgAHAAAA+f/7/wAAAwAFAP7/+P/8/wIAAgACAP3/+f/9/wMAAwAAAPz/+v/+/wQAAgD+//z/+/8AAAQAAQD8//z//f8BAAQAAAD7//3///8BAAMA///7//3/AAACAAEA/v/7//7/AQABAAAA/v/9////AQAAAP///v/+////AAD/////////////////////////////","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD////////+//////8AAAAA/v/+/wAAAQD8//3/CQAJAP3/+v8PAAcApABlABkBkwCO/i//lfqa/HQAcf/3BdkCzwJcBCMC0wMN/9/9wgI7AaECYfxV/Tf83vhn/xrt8Owx/8n7cgHABYb43QcZDh4WugNrA7P74gHu/9z/zv0t/acCiQHY/iv4qQOl/ysCE/0//XT9Sf4O//j9xfupAn394gHO+rsCXAFIAxQC9wIXBgcD2AQuAnb/9gJh/6wAVfxEAI4Bvf7oAFv/bALsAMQBe/88/joAT/4dAH39/v9LAXn/gwDI//QBdABcAA0A7f4lAMn///+9/tv/iABp/13/pP/dALv/w/8MAHv//f+y/6////7U/5AAZP+Z/8r/nQDR/5r/DwDr/xAA4v+s/3z/+P9uAOv/t/82AGcAHgCb/yQAFQBGAM7/CgD3/xoAegAaAOz/CgBHAA8Adv8/AAAABQC2/xIAAAA7ABQAKgCj/z4AAQAXAJz/JAADAAcA8f/1/2AAAQAlAPD/NgDx/1wA7v/4/wMAZADv//3/HQAkAFoA8P9FAPv/FgBIAPf/WQAHAEUACQD0/xIAQwDu/wMAwP9VALn/XwCw/yEA5f8sAPj/FgDD/1YAyv8rAOX/HQDo//j/IQAQACAAHwD9/yQAHQBAABgABQAiAAUAKAD3/wkACwAKAAMABwAJAPb/+f8GAOr/JQAHABMA6P8TAA4AGgD//woA8/8ZAP//GADu/w0A9v8SAAMABwD4/wQA5P8XAAQACgDq/wUA+/8VAAcACADs/xIAAAATAPH/+v/1//T/7f///+z/+v/y/+//9/8KAAcACgAJAPT/BAAKAAAABgAIAPL/9v8KAAMABAACAPr/9v8OAAIA+P/x//v/+f8MAPb/+P/w/wQA9f8MAPn////7/woA/v8PAAEAAgD1/xAAAQAPAP//AwD//xQABwALAAAABgADABAAAgAHAAAACAABAA8ABQAFAAMABwAEAA4ABwADAAEACQAFAAoAAwD//wAACQADAAUAAQD/////CAABAAMAAAD/////BwACAAEAAAD/////BwACAP7///8BAAAABgABAP7///8CAAAABAAAAP7///8DAAAAAwAAAP3///8DAAAAAQAAAP3//v8EAAAAAAD+//////8EAP/////+/wAA/v8EAP/////+/wEA/v8EAP///v/+/wIA//8DAP///v/+/wIA//8BAP///v/+/wMA//8BAP/////+/wMA//8AAP//AAD+/wQA//8AAP7/AQD//wIA////////AQD//wIA////////AQAAAAEAAAAAAP//AQD//wEAAAAAAP//AQAAAAEAAAAAAAAA","UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD+/wAA+v8AAPz/AAD//wAA/f8AAAEAAAD+/wAACQAAAAQAAAAZAAAAtgAAAFsBAABW/gAAH/oAAGcBAABoBwAAlAAAAO3/AAARAQAA+wIAAEoEAACe/gAAiv4AALD0AADJ8wAAkQQAAF34AABi8QAAPQAAAAH2AAD19AAADAMAAJwGAACTEAAA0AwAAJkHAACOBwAAuQEAANcDAAC6AgAAHwUAAHEFAAB0AwAAbgEAADz+AADYAQAAGAAAAJwCAADgAAAA//0AAMn+AAAT/AAAwP8AAOn9AAAJAAAAewEAAOn+AACN/wAAOv0AAO3+AADN/gAAcP8AACj/AACq/gAA+f4AAML9AACa/wAA/f4AAN7/AABo/wAA6/4AAE//AAAC/wAAEQAAAHX/AAB0AAAA5f8AAEwAAAB3AAAA5/8AAMIAAABCAAAAzgAAAE8AAAB3AAAAKAAAADMAAACqAAAALwAAAK4AAAASAAAAVgAAACgAAAAtAAAATAAAAP3/AAA7AAAA2/8AACQAAADw/wAALQAAADEAAAAlAAAAbAAAADMAAABUAAAAEAAAACgAAAD1/wAA9v8AAPr/AADu/wAALgAAABIAAABUAAAARAAAAGUAAABGAAAAOAAAAGAAAAAuAAAARQAAACEAAAAfAAAAAAAAAAkAAAAQAAAAAwAAABIAAADs/wAAEAAAAAYAAAASAAAAIgAAABEAAAADAAAABAAAAA8AAAD4/wAAHQAAAAsAAAAIAAAADgAAAP//AAAcAAAADwAAAAYAAAASAAAAFwAAAAMAAAAYAAAAEgAAAPr/AAAQAAAADQAAAAoAAAD3/wAABgAAAPb/AADf/wAA/v8AAPL/AAD6/wAAFAAAAAQAAAAEAAAAGwAAAAEAAAAMAAAAIAAAAAIAAAAdAAAAGAAAAAIAAAAcAAAAEgAAAAcAAAAeAAAADwAAAAQAAAAeAAAABAAAAAYAAAAZAAAAAQAAAA4AAAATAAAA/v8AAAoAAAAOAAAA+/8AAAsAAAAJAAAA+f8AAAsAAAABAAAA+f8AAAoAAAD9/wAA+v8AAAcAAAD5/wAA+v8AAAUAAAD3/wAA/f8AAAQAAAD2/wAAAAAAAAEAAAD3/wAAAgAAAAAAAAD4/wAAAwAAAP7/AAD6/wAABAAAAP3/AAD8/wAABAAAAPv/AAD+/wAAAwAAAPv/AAD//wAAAQAAAPv/AAAAAAAAAAAAAPv/AAACAAAA//8AAPz/AAACAAAA/v8AAP3/AAACAAAA/f8AAP7/AAABAAAA/f8AAP//AAABAAAA/f8AAAAAAAAAAAAA/v8AAAEAAAAAAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA"]},function(A,t,e){"use strict";t.getBrowserInfo=function(){const A=navigator.userAgent;let t,e=A.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*([\d\.]+)/i)||[];if(/trident/i.test(e[1]))return t=/\brv[ :]+(\d+)/g.exec(A)||[],{name:"IE",version:t[1]||""};if("Chrome"===e[1]&&null!=(t=A.match(/\bOPR|Edge\/(\d+)/)))return{name:"Opera",version:t[1]};e=e[2]?[e[1],e[2]]:[navigator.appName,navigator.appVersion,"-?"],null!=(t=A.match(/version\/([\d.]+)/i))&&e.splice(1,1,t[1]);let i=A.match(/android|ipad|iphone/i);return i||(i=A.match(/cros|linux|mac os x|windows/i)),{name:e[0],version:e[1],platform:i?i[0]:"unknown"}},t.patchSafari=function(){window.webkitAudioContext&&window.webkitOfflineAudioContext&&(window.AudioContext=window.webkitAudioContext,window.OfflineAudioContext=window.webkitOfflineAudioContext)}},function(A,t,e){"use strict";A.exports="1.0.6"}])})},function(A,t,e){"use strict";A.exports="0.0.4"}])}); \ No newline at end of file diff --git a/src/renderer/js/showdown.min.js b/src/renderer/lib/showdown.min.js similarity index 100% rename from src/renderer/js/showdown.min.js rename to src/renderer/lib/showdown.min.js diff --git a/src/renderer/js/smoothscroll.js b/src/renderer/lib/smoothscroll.js similarity index 100% rename from src/renderer/js/smoothscroll.js rename to src/renderer/lib/smoothscroll.js diff --git a/src/renderer/js/sortable.min.js b/src/renderer/lib/sortable.min.js similarity index 100% rename from src/renderer/js/sortable.min.js rename to src/renderer/lib/sortable.min.js diff --git a/src/renderer/js/velocity.min.js b/src/renderer/lib/velocity.min.js similarity index 100% rename from src/renderer/js/velocity.min.js rename to src/renderer/lib/velocity.min.js diff --git a/src/renderer/js/vue-horizontal.js b/src/renderer/lib/vue-horizontal.js similarity index 100% rename from src/renderer/js/vue-horizontal.js rename to src/renderer/lib/vue-horizontal.js diff --git a/src/renderer/js/vue-observe-visibility.min.js b/src/renderer/lib/vue-observe-visibility.min.js similarity index 100% rename from src/renderer/js/vue-observe-visibility.min.js rename to src/renderer/lib/vue-observe-visibility.min.js diff --git a/src/renderer/js/vue.dev.js b/src/renderer/lib/vue.dev.js similarity index 100% rename from src/renderer/js/vue.dev.js rename to src/renderer/lib/vue.dev.js diff --git a/src/renderer/js/vue.js b/src/renderer/lib/vue.js similarity index 100% rename from src/renderer/js/vue.js rename to src/renderer/lib/vue.js diff --git a/src/renderer/js/vuedraggable.umd.min.js b/src/renderer/lib/vuedraggable.umd.min.js similarity index 100% rename from src/renderer/js/vuedraggable.umd.min.js rename to src/renderer/lib/vuedraggable.umd.min.js diff --git a/src/renderer/js/vuex.min.js b/src/renderer/lib/vuex.min.js similarity index 100% rename from src/renderer/js/vuex.min.js rename to src/renderer/lib/vuex.min.js diff --git a/src/renderer/main/app.js b/src/renderer/main/app.js new file mode 100644 index 00000000..a5bfe938 --- /dev/null +++ b/src/renderer/main/app.js @@ -0,0 +1,28 @@ +import { app } from "./vueapp.js" +import {CiderCache} from './cidercache.js' +import {CiderFrontAPI} from './ciderfrontapi.js' +import {simulateGamepad} from './gamepad.js' +import {CiderAudio} from '../audio/audio.js' +import {Events} from './events.js' +import { wsapi } from "./wsapi_interop.js" + + +// Define window objects +window.app = app +window.CiderAudio = CiderAudio +window.CiderCache = CiderCache +window.CiderFrontAPI = CiderFrontAPI +window.wsapi = wsapi + +// Mount Vue to #app +app.$mount("#app") + +// Init CiderAudio +if (app.cfg.advanced.AudioContext){ + CiderAudio.init() +} + +// Import gamepad support +app.simulateGamepad = simulateGamepad + +Events.InitEvents() \ No newline at end of file diff --git a/src/renderer/main/cidercache.js b/src/renderer/main/cidercache.js new file mode 100644 index 00000000..c28550c1 --- /dev/null +++ b/src/renderer/main/cidercache.js @@ -0,0 +1,24 @@ +const CiderCache = { + async getCache(file) { + let cache = await ipcRenderer.sendSync("get-cache", file) + if (isJson(cache)) { + cache = JSON.parse(cache) + if (Object.keys(cache).length === 0) { + cache = false + } + } else { + cache = false + } + return cache + }, + async putCache(file, data) { + console.log(`Caching ${file}`) + ipcRenderer.invoke("put-cache", { + file: file, + data: JSON.stringify(data) + }) + return true + } +} + +export {CiderCache} \ No newline at end of file diff --git a/src/renderer/main/ciderfrontapi.js b/src/renderer/main/ciderfrontapi.js new file mode 100644 index 00000000..fc51f884 --- /dev/null +++ b/src/renderer/main/ciderfrontapi.js @@ -0,0 +1,16 @@ +const CiderFrontAPI = { + Objects: { + MenuEntry: function () { + this.id = "" + this.name = "" + this.onClick = () => { + } + } + }, + AddMenuEntry(entry) { + app.pluginMenuEntries.push(entry) + app.pluginInstalled = true + } +} + +export {CiderFrontAPI} \ No newline at end of file diff --git a/src/renderer/main/events.js b/src/renderer/main/events.js new file mode 100644 index 00000000..23236e41 --- /dev/null +++ b/src/renderer/main/events.js @@ -0,0 +1,74 @@ +const Events = { + InitEvents() { + const app = window.app + // Key binds + document.addEventListener('keydown', function (e) { + if (e.keyCode === 70 && e.ctrlKey) { + app.$refs.searchInput.focus() + app.$refs.searchInput.select() + } + }); + +// add event listener for when window.location.hash changes + window.addEventListener("hashchange", function () { + app.appRoute(window.location.hash) + }); + + // Key bind to unjam MusicKit in case it fails: CTRL+F10 + + document.addEventListener('keydown', function (event) { + if (event.ctrlKey && event.keyCode == 121) { + try { + app.mk._services.mediaItemPlayback._currentPlayer.stop() + } catch (e) { + } + try { + app.mk._services.mediaItemPlayback._currentPlayer.destroy() + } catch (e) { + } + } + }); + + window.addEventListener("mouseup", (e) => { + if (e.button === 3) { + e.preventDefault() + app.navigateBack() + } else if (e.button === 4) { + e.preventDefault() + app.navigateForward() + } + }); + + document.addEventListener('keydown', function (event) { + if (event.ctrlKey && event.keyCode == 122) { + try { + ipcRenderer.send('detachDT', '') + } catch (e) { + } + } + }); + + // Hang Timer + app.hangtimer = setTimeout(() => { + if (confirm("Cider is not responding. Reload the app?")) { + window.location.reload() + } + }, 10000) + +// Refresh Focus + function refreshFocus() { + if (document.hasFocus() == false) { + app.windowFocus(false) + } else { + app.windowFocus(true) + } + setTimeout(refreshFocus, 200); + } + + app.getHTMLStyle() + + refreshFocus(); + } +} + +export {Events} \ No newline at end of file diff --git a/src/renderer/main/gamepad.js b/src/renderer/main/gamepad.js new file mode 100644 index 00000000..2c813df3 --- /dev/null +++ b/src/renderer/main/gamepad.js @@ -0,0 +1,326 @@ +function simulateGamepad () { + const app = window.app + app.chrome.showCursor = true + let cursorPos = [0, 0]; + let intTabIndex = 0 + const cursorSpeedPvt = 8 + const cursorSize = 16 + let scrollSpeed = 8 + let buttonPressDelay = 500 + let stickDeadZone = 0.2 + let scrollGroup = null + let scrollGroupY = null + let elementFocusEnabled = true + + let cursorSpeed = cursorSpeedPvt + + let lastButtonPress = { + + } + + var sounds = { + Confirm: new Audio("./sounds/confirm.ogg"), + Menu: new Audio("./sounds/btn1.ogg"), + Hover: new Audio("./sounds/hover.ogg") + } + + let element = document.elementFromPoint(0, 0) + let elementType = 0 + + function appLoop() { + var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []); + if (!gamepads) { + return; + } + + var gp = gamepads[0]; + + // LEFT STICK + if (gp.axes[0] > stickDeadZone) { + cursorPos[0] += (gp.axes[0] * cursorSpeed) + } else if (gp.axes[0] < -stickDeadZone) { + cursorPos[0] += (gp.axes[0] * cursorSpeed) + } + + if (gp.axes[1] > stickDeadZone) { + cursorPos[1] += (gp.axes[1] * cursorSpeed) + } else if (gp.axes[1] < -stickDeadZone) { + cursorPos[1] += (gp.axes[1] * cursorSpeed) + } + + if (cursorPos[0] < cursorSize) { + cursorPos[0] = cursorSize + } + if (cursorPos[1] < cursorSize) { + cursorPos[1] = cursorSize + } + if (cursorPos[0] > window.innerWidth - cursorSize) { + cursorPos[0] = window.innerWidth - cursorSize + } + if (cursorPos[1] > window.innerHeight - cursorSize) { + cursorPos[1] = window.innerHeight - cursorSize + } + + + // RIGHT STICK. + if (scrollGroupY) { + if (gp.axes[3] > stickDeadZone) { + $(scrollGroupY).scrollTop($(scrollGroupY).scrollTop() + (gp.axes[3] * scrollSpeed)) + elementFocusEnabled = false + } else if (gp.axes[3] < -stickDeadZone) { + $(scrollGroupY).scrollTop($(scrollGroupY).scrollTop() + (gp.axes[3] * scrollSpeed)) + elementFocusEnabled = false + } else { + elementFocusEnabled = true + } + } + + + + if (scrollGroup) { + if (gp.axes[2] > stickDeadZone) { + $(scrollGroup).scrollLeft($(scrollGroup).scrollLeft() + (gp.axes[2] * scrollSpeed)) + elementFocusEnabled = false + } else if (gp.axes[2] < -stickDeadZone) { + $(scrollGroup).scrollLeft($(scrollGroup).scrollLeft() + (gp.axes[2] * scrollSpeed)) + elementFocusEnabled = false + } else { + elementFocusEnabled = true + } + } + + + $(".cursor").css({ + top: cursorPos[1] + "px", + left: cursorPos[0] + "px", + display: "block" + }) + + // A BUTTON + if (gp.buttons[0].pressed) { + if (!lastButtonPress["A"]) { + lastButtonPress["A"] = 0 + } + if (Date.now() - lastButtonPress["A"] > buttonPressDelay) { + lastButtonPress["A"] = Date.now() + sounds.Confirm.play() + if (elementType == 0) { + document.activeElement.dispatchEvent(new Event("click")) + document.activeElement.dispatchEvent(new Event("controller-click")) + } else { + element.dispatchEvent(new Event("click")) + element.dispatchEvent(new Event("controller-click")) + } + } + } + + // B BUTTON + if (gp.buttons[1].pressed) { + + if (!lastButtonPress["B"]) { + lastButtonPress["B"] = 0 + } + if (Date.now() - lastButtonPress["B"] > buttonPressDelay) { + lastButtonPress["B"] = Date.now() + if (elementType == 0) { + document.activeElement.dispatchEvent(new Event("contextmenu")) + setTimeout(() => { + if ($(".menu-option").length > 0) { + let bounds = $(".menu-option")[0].getBoundingClientRect() + cursorPos[0] = bounds.left + (bounds.width / 2) + cursorPos[1] = bounds.top + (bounds.height / 2) + } + }, 100) + } else { + element.dispatchEvent(new Event("contextmenu")) + } + } + + } + + // right bumper + if (gp.buttons[5].pressed) { + if (!lastButtonPress["RB"]) { + lastButtonPress["RB"] = 0 + } + if (Date.now() - lastButtonPress["RB"] > buttonPressDelay) { + lastButtonPress["RB"] = Date.now() + app.navigateForward() + + } + } + + // left bumper + if (gp.buttons[4].pressed) { + if (!lastButtonPress["LB"]) { + lastButtonPress["LB"] = 0 + } + if (Date.now() - lastButtonPress["LB"] > buttonPressDelay) { + lastButtonPress["LB"] = Date.now() + app.navigateBack() + + } + } + + + + // cursor hover + if (elementFocusEnabled) { + element = document.elementFromPoint(cursorPos[0], cursorPos[1]) + } + + if (element) { + + let closest = element.closest("[tabindex], input, button, a") + + // VERT SCROLL + let scrollGroupCloY = element.closest(`[scrollaxis="y"]`) + if (scrollGroupCloY) { + scrollGroupY = scrollGroupCloY + } + + + // HOZ SCROLL + let scrollGroupClo = element.closest(".v-hl-container") + + if (scrollGroupClo) { + if (scrollGroupClo.classList.contains("v-hl-container")) { + scrollGroup = scrollGroupClo + scrollGroup.style["scroll-snap-type"] = "unset" + } else { + scrollGroup.style["scroll-snap-type"] = "" + scrollGroup = null + } + } + + if (closest) { + elementType = 0 + closest.focus() + } else { + if (closest) { + closest.blur() + } + elementType = 1 + element.focus() + } + cursorSpeed = cursorSpeedPvt + if (!element.classList.contains("app-chrome") + && !element.classList.contains("app-content")) { + cursorSpeed = cursorSpeedPvt + } + // console.log($._data($(element), "events")) + } else { + cursorSpeed = 12 + } + // console.log(gp.axes[0], gp.axes[1]) + start = requestAnimationFrame(appLoop); + } + +// controller pairing + notyf.error("Press the button on your controller to pair it to Cider.") + window.addEventListener("gamepadconnected", function (e) { + console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", + e.gamepad.index, e.gamepad.id, + e.gamepad.buttons.length, e.gamepad.axes.length); + notyf.success("Pairing successful!") + appLoop() + }, { once: true }); + + document.addEventListener("keydown", (e) => { + sounds.Confirm.currentTime = 0 + sounds.Menu.currentTime = 0 + sounds.Hover.currentTime = 0 + let tabbable = $("[tabindex]") + console.log(e.key) + switch (e.key) { + default: + break; + case "ArrowLeft": + e.preventDefault() + + cursorPos[0] -= cursorSpeed + break; + case "ArrowRight": + e.preventDefault() + + cursorPos[0] += cursorSpeed + break; + case "ArrowUp": + e.preventDefault() + + cursorPos[1] -= cursorSpeed + // sounds.Hover.play() + // if(intTabIndex <= 0) { + // intTabIndex = 0 + // }else{ + // intTabIndex-- + // } + // $(tabbable[intTabIndex]).focus() + // $("#app-content").scrollTop($(document.activeElement).offset().top) + break; + case "ArrowDown": + e.preventDefault() + + cursorPos[1] += cursorSpeed + // if(intTabIndex < tabbable.length) { + // intTabIndex++ + // }else{ + // intTabIndex = tabbable.length + // } + // $(tabbable[intTabIndex]).focus() + // $("#app-content").scrollTop($(document.activeElement).offset().top) + break; + case "c": + app.resetState() + break; + case "x": + // set cursorPos to the top right of the screen + // sounds.Menu.play() + if (elementType == 0) { + document.activeElement.dispatchEvent(new Event("contextmenu")) + } else { + element.dispatchEvent(new Event("contextmenu")) + } + + e.preventDefault() + break; + case "z": + sounds.Confirm.play() + if (elementType == 0) { + document.activeElement.dispatchEvent(new Event("click")) + document.activeElement.dispatchEvent(new Event("controller-click")) + } else { + element.dispatchEvent(new Event("click")) + element.dispatchEvent(new Event("controller-click")) + } + + e.preventDefault() + break; + } + + $(".cursor").css({ + top: cursorPos[1] + "px", + left: cursorPos[0] + "px" + }) + function lerp(a, b, n) { + return (1 - n) * a + n * b + } + + + element = document.elementFromPoint(cursorPos[0], cursorPos[1]) + + if (element) { + let closest = element.closest("[tabindex], input, button, a") + if (closest) { + elementType = 0 + closest.focus() + } else { + elementType = 1 + element.focus() + } + } + console.log(element) + }); +} + +export {simulateGamepad} \ No newline at end of file diff --git a/src/renderer/main/vueapp.js b/src/renderer/main/vueapp.js new file mode 100644 index 00000000..186b62ce --- /dev/null +++ b/src/renderer/main/vueapp.js @@ -0,0 +1,4070 @@ +import {store} from './vuex-store.js'; + +Vue.use(VueHorizontal); +Vue.use(VueObserveVisibility); + +const app = new Vue({ + store: store, + data: { + version: ipcRenderer.sendSync("get-version"), + appMode: "player", + ipcRenderer: ipcRenderer, + cfg: ipcRenderer.sendSync("getStore"), + isDev: ipcRenderer.sendSync("is-dev"), + drawertest: false, + platform: "", + mk: {}, + quickPlayQuery: "", + pluginInstalled: false, + pluginMenuEntries: [], + lz: ipcRenderer.sendSync("get-i18n", "en_US"), + lzListing: ipcRenderer.sendSync("get-i18n-listing"), + search: { + term: "", + hints: [], + showHints: false, + results: {}, + resultsSocial: {}, + limit: 10 + }, + fullscreenLyrics: false, + playerLCD: { + playbackDuration: 0, + desiredDuration: 0, + userInteraction: false + }, + drawer: { + open: false, + panel: "" + }, + browsepage: [], + listennow: [], + madeforyou: [], + radio: { + personal: [] + }, + mklang: 'en', + webview: { + url: "", + title: "", + loading: false + }, + showingPlaylist: [], + appleCurator: [], + artistPage: { + data: {}, + }, + library: { + backgroundNotification: { + show: false, + message: "", + total: 0, + progress: 0 + }, + songs: { + sortingOptions: { + "albumName": "0", + "artistName": "0", + "name": "0", + "genre": "0", + "releaseDate": "0", + "durationInMillis": "0", + "dateAdded": "0" + }, + sorting: "name", + sortOrder: "asc", + listing: [], + meta: { total: 0, progress: 0 }, + search: "", + displayListing: [], + downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library + }, + albums: { + sortingOptions: { + "albumName": "0", + "artistName": "0", + "name": "0", + "genre": "0" + }, + viewAs: 'covers', + sorting: ["dateAdded", "name"], // [0] = recentlyadded page, [1] = albums page + sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page + listing: [], + meta: { total: 0, progress: 0 }, + search: "", + displayListing: [], + downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library + }, + artists: { + sortingOptions: { + "artistName": "0", + "name": "0", + "genre": "0", + "releaseDate": "0" + }, + viewAs: 'covers', + sorting: ["dateAdded", "name"], // [0] = recentlyadded page, [1] = albums page + sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page + listing: [], + meta: { total: 0, progress: 0 }, + search: "", + displayListing: [], + downloadState: 0 // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library + }, + }, + playlists: { + listing: [], + details: {}, + loadingState: 0, // 0 loading, 1 loaded, 2 error + id: "", + trackMapping: {} + }, + webremoteurl: "", + webremoteqr: "", + mxmtoken: "", + mkIsReady: false, + playerReady: false, + animateBackground: false, + currentArtUrl: '', + currentArtUrlRaw: '', + lyricon: false, + currentTrackID: '', + currentTrackIDBG: '', + lyrics: [], + currentLyricsLine: 0, + lyriccurrenttime: 0, + richlyrics: [], + lyricsMediaItem: {}, + lyricsDebug: { + current: 0, + start: 0, + end: 0 + }, + v3: { + requestBody: { + platform: "web" + } + }, + tmpHeight: '', + tmpWidth: '', + tmpVar: [], + notification: false, + chrome: { + appliedTheme: { + location: "", + info: {} + }, + desiredPageTransition: "wpfade_transform", + hideUserInfo: ipcRenderer.sendSync("is-dev") || false, + artworkReady: false, + userinfo: { + "id": "", + "attributes": { + "name": "Cider User", + "handle": "CiderUser", + "artwork": { "url": "./assets/logocut.png" } + } + }, + menuOpened: false, + maximized: false, + drawerOpened: false, + drawerState: "queue", + topChromeVisible: true, + progresshover: false, + windowControlPosition: "right", + contentAreaScrolling: true, + showCursor: false + }, + collectionList: { + response: {}, + title: "", + type: "" + }, + prevButtonBackIndicator: false, + currentSongInfo: {}, + page: "", + pageHistory: [], + songstest: false, + hangtimer: null, + selectedMediaItems: [], + routes: ["browse", "listen_now", "radio"], + musicBaseUrl: "https://api.music.apple.com/", + modals: { + addToPlaylist: false, + spatialProperties: false, + qrcode: false, + equalizer: false, + audioSettings: false, + pluginMenu: false, + audioControls: false, + showPlaylist: false, + castMenu: false + }, + socialBadges: { + badgeMap: {}, + version: "", + mediaItems: [], + mediaItemDLState: 0 // 0 = not started, 1 = in progress, 2 = complete + }, + menuPanel: { + visible: false, + event: null, + content: { + name: "", + items: {}, + headerItems: {} + } + }, + pauseButtonTimer: null, + activeCasts: [] + }, + watch: { + cfg: { + handler: function (val, oldVal) { + console.log(`cfg changed from ${oldVal} to ${val}`); + ipcRenderer.send("setStore", val); + }, + deep: true + }, + page: () => { + document.getElementById("app-content").scrollTo(0, 0); + app.resetState() + }, + showingPlaylist: () => { + if (!app.modals.showPlaylist) { + document.getElementById("app-content").scrollTo(0, 0); + app.resetState() + } + }, + artistPage: () => { + document.getElementById("app-content").scrollTo(0, 0); + app.resetState() + } + }, + methods: { + songLinkShare(amUrl) { + notyf.open({ type: "info", className: "notyf-info", message: app.getLz('term.song.link.generate') }) + let self = this + let httpRequest = new XMLHttpRequest(); + httpRequest.open('GET', `https://api.song.link/v1-alpha.1/links?url=${amUrl}&userCountry=US`, true); + httpRequest.send(); + httpRequest.onreadystatechange = function () { + if (httpRequest.readyState === 4) { + if (httpRequest.status === 200) { + let response = JSON.parse(httpRequest.responseText); + console.log(response); + self.copyToClipboard(response.pageUrl) + } else { + console.log('There was a problem with the request.'); + notyf.error(app.getLz('term.requestError')) + } + } + } + }, + mainMenuVisibility(val) { + if (val) { + (this.mk.isAuthorized) ? this.chrome.menuOpened = !this.chrome.menuOpened : false; + if (!this.mk.isAuthorized) { + this.mk.authorize() + } + } else { + setTimeout(() => { + this.chrome.menuOpened = false + }, 100) + } + }, + stringTemplateParser(expression, valueObj) { + const templateMatcher = /{{\s?([^{}\s]*)\s?}}/g; + let text = expression.replace(templateMatcher, (substring, value, index) => { + value = valueObj[value]; + return value; + }); + return text + // stringTemplateParser('my name is {{name}} and age is {{age}}', {name: 'Tom', age:100}) + }, + async setLz(lang) { + if (lang == "") { + lang = this.cfg.general.language + } + this.lz = ipcRenderer.sendSync("get-i18n", lang) + this.mklang = await this.MKJSLang() + }, + getLz(message, options = {}) { + if (this.lz[message]) { + if (options["count"]) { + if (typeof this.lz[message] === "object") { + let type = window.fastPluralRules.getPluralFormNameForCardinalByLocale(this.cfg.general.language.replace("_", "-"), options["count"]); + return this.lz[message][type] ?? ((this.lz[message])[Object.keys(this.lz[message])[0]] ?? this.lz[message]) + } else { + // fallback English plural forms ( old i18n ) + if (options["count"] > 1) { + return this.lz[message + "s"] ?? this.lz[message] + } else { + return this.lz[message] ?? this.lz[message + "s"] + } + } + } else if (typeof this.lz[message] === "object") { + return (this.lz[message])[Object.keys(this.lz[message])[0]] + } + return this.lz[message] + } else { + return message + } + }, + setLzManual() { + app.$data.library.songs.sortingOptions = { + "albumName": app.getLz('term.sortBy.album'), + "artistName": app.getLz('term.sortBy.artist'), + "name": app.getLz('term.sortBy.name'), + "genre": app.getLz('term.sortBy.genre'), + "releaseDate": app.getLz('term.sortBy.releaseDate'), + "durationInMillis": app.getLz('term.sortBy.duration'), + "dateAdded": app.getLz('term.sortBy.dateAdded') + } + + app.$data.library.albums.sortingOptions = { + "albumName": app.getLz('term.sortBy.album'), + "artistName": app.getLz('term.sortBy.artist'), + "name": app.getLz('term.sortBy.name'), + "genre": app.getLz('term.sortBy.genre') + } + + app.$data.library.artists.sortingOptions = { + "artistName": app.getLz('term.sortBy.artist'), + "name": app.getLz('term.sortBy.name'), + "genre": app.getLz('term.sortBy.genre'), + "releaseDate": app.getLz('term.sortBy.releaseDate') + } + }, + async showSocialListeningTo() { + let contentIds = Object.keys(app.socialBadges.badgeMap) + app.showCollection({ data: this.socialBadges.mediaItems }, "Friends Listening To", "albums") + if (this.socialBadges.mediaItemDLState == 1 || this.socialBadges.mediaItemDLState == 2) { + return + } + this.socialBadges.mediaItemDLState = 2 + await asyncForEach(contentIds, async (item) => { + try { + let type = "albums" + if (item.includes("pl.")) { + type = "playlists" + } + if (item.includes("ra.")) { + type = "stations" + } + let found = await app.mk.api.v3.music(`/v1/catalog/us/${type}/${item}`) + this.socialBadges.mediaItems.push(found.data.data[0]) + } catch (e) { + + } + }) + }, + async openAppleMusicURL(url) { + let properties = MusicKit.formattedMediaURL(url) + let item = { + id: properties.contentId, + attributes: { + playParams: { + id: properties.contentId, + kind: properties.kind, + } + }, + type: properties.kind, + kind: properties.kind + } + app.routeView(item) + }, + saveFile(fileName, urlFile) { + let a = document.createElement("a"); + a.style = "display: none"; + document.body.appendChild(a); + a.href = urlFile; + a.download = fileName; + a.click(); + window.URL.revokeObjectURL(url); + a.remove(); + }, + async showMenuPanel(data, event) { + app.menuPanel.visible = true; + app.menuPanel.content.name = data.name ?? ""; + app.menuPanel.content.items = data.items ?? {}; + app.menuPanel.content.headerItems = data.headerItems ?? {}; + if (event) { + app.menuPanel.event = event; + } + }, + async getSvgIcon(url) { + let response = await fetch(url); + let data = await response.text(); + return data; + }, + getSocialBadges(cb = () => { + }) { + let self = this + try { + app.mk.api.v3.music("/v1/social/badging-map").then(data => { + self.socialBadges.badgeMap = data.data.results.badgingMap + cb(data.data.results.badgingMap) + }) + } catch (ex) { + this.socialBadges.badgeMap = {} + } + }, + addFavorite(id, type) { + this.cfg.home.favoriteItems.push({ + id: id, + type: type + }); + }, + modularUITest(val = false) { + this.fullscreenLyrics = val; + if (val) { + document.querySelector("#app-main").classList.add("modular-fs") + } else { + document.querySelector("#app-main").classList.remove("modular-fs") + } + }, + navigateBack() { + this.chrome.desiredPageTransition = "wpfade_transform_backwards" + return new Promise((resolve, reject) => { + history.back() + setTimeout(() => { + + + resolve(this.chrome.desiredPageTransition = "wpfade_transform") + }, 100) + }) + }, + navigateForward() { + history.forward() + }, + getHTMLStyle() { + // document.querySelector("html").style.background = "#222"; + document.querySelector("body").classList.add("notransparency") + }, + resetState() { + this.menuPanel.visible = false; + app.selectedMediaItems = []; + this.chrome.contentAreaScrolling = true + for (let key in app.modals) { + app.modals[key] = false; + } + }, + promptAddToPlaylist() { + app.modals.addToPlaylist = true; + }, + async addSelectedToNewPlaylist() { + let self = this + let pl_items = [] + for (let i = 0; i < self.selectedMediaItems.length; i++) { + if (self.selectedMediaItems[i].kind == "song" || self.selectedMediaItems[i].kind == "songs") { + self.selectedMediaItems[i].kind = "songs" + pl_items.push({ + id: self.selectedMediaItems[i].id, + type: self.selectedMediaItems[i].kind + }) + } else if ((self.selectedMediaItems[i].kind == "album" || self.selectedMediaItems[i].kind == "albums") && self.selectedMediaItems[i].isLibrary != true) { + self.selectedMediaItems[i].kind = "albums" + let res = await self.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/albums/${self.selectedMediaItems[i].id}/tracks`); + let ids = res.data.data.map(function (i) { + return { id: i.id, type: i.type } + }) + pl_items = pl_items.concat(ids) + } else if (self.selectedMediaItems[i].kind == "library-song" || self.selectedMediaItems[i].kind == "library-songs") { + self.selectedMediaItems[i].kind = "library-songs" + pl_items.push({ + id: self.selectedMediaItems[i].id, + type: self.selectedMediaItems[i].kind + }) + } 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" + let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`); + let ids = res.data.data.map(function (i) { + return { id: i.id, type: i.type } + }) + pl_items = pl_items.concat(ids) + } else { + pl_items.push({ + id: self.selectedMediaItems[i].id, + type: self.selectedMediaItems[i].kind + }) + } + } + this.modals.addToPlaylist = false + app.newPlaylist(app.getLz('term.newPlaylist'), pl_items) + }, + async addSelectedToPlaylist(playlist_id) { + let self = this + let pl_items = [] + for (let i = 0; i < self.selectedMediaItems.length; i++) { + if (self.selectedMediaItems[i].kind == "song" || self.selectedMediaItems[i].kind == "songs") { + self.selectedMediaItems[i].kind = "songs" + pl_items.push({ + id: self.selectedMediaItems[i].id, + type: self.selectedMediaItems[i].kind + }) + } else if ((self.selectedMediaItems[i].kind == "album" || self.selectedMediaItems[i].kind == "albums") && self.selectedMediaItems[i].isLibrary != true) { + self.selectedMediaItems[i].kind = "albums" + let res = await self.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/albums/${self.selectedMediaItems[i].id}/tracks`); + let ids = res.data.data.map(function (i) { + return { id: i.id, type: i.type } + }) + pl_items = pl_items.concat(ids) + } else if (self.selectedMediaItems[i].kind == "library-song" || self.selectedMediaItems[i].kind == "library-songs") { + self.selectedMediaItems[i].kind = "library-songs" + pl_items.push({ + id: self.selectedMediaItems[i].id, + type: self.selectedMediaItems[i].kind + }) + } 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" + let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`); + let ids = res.data.data.map(function (i) { + return { id: i.id, type: i.type } + }) + pl_items = pl_items.concat(ids) + } else { + pl_items.push({ + id: self.selectedMediaItems[i].id, + type: self.selectedMediaItems[i].kind + }) + } + + } + this.modals.addToPlaylist = false + 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) { + this.getPlaylistFromID(this.showingPlaylist.id, true) + } + }) + }, + async init() { + let self = this + if (this.cfg.visual.theme != "default.less" && this.cfg.visual.theme != "") { + this.setTheme(this.cfg.visual.theme) + } + this.setLz(this.cfg.general.language) + this.setLzManual() + clearTimeout(this.hangtimer) + this.mk = MusicKit.getInstance() + let needsReload = (typeof localStorage["music.ampwebplay.media-user-token"] == "undefined") + this.mk.authorize().then(() => { + self.mkIsReady = true + if (needsReload) { + document.location.reload() + } + }) + this.$forceUpdate() + if (this.isDev) { + this.mk.privateEnabled = true + // Hide UserInfo if Dev mode + } else { + // Get Hide User from Settings + this.chrome.hideUserInfo = !this.cfg.visual.showuserinfo + this.mk.privateEnabled = this.cfg.general.privateEnabled + } + if (this.cfg.visual.hw_acceleration == "disabled") { + document.body.classList.add("no-gpu") + } + this.mk._services.timing.mode = 0 + this.platform = ipcRenderer.sendSync('cider-platform'); + + this.mklang = await this.MKJSLang() + + try { + // Set profile name + this.chrome.userinfo = (await app.mk.api.v3.music(`/v1/me/social-profile`)).data.data[0] + } catch (err) { + } + + this.mk._bag.features['seamless-audio-transitions'] = this.cfg.audio.seamless_audio + + // API Fallback + if (!this.chrome.userinfo) { + this.chrome.userinfo = { + "id": "", + "attributes": { + "name": "Cider User", + "handle": "CiderUser", + "artwork": { "url": "./assets/logocut.png" } + } + } + } + MusicKitInterop.init() + // Set the volume + + // Check the value of this.cfg.audio.muted + if (!this.cfg.audio.muted) { + // Set the mk.volume to the last stored volume data + this.mk.volume = this.cfg.audio.volume + } else if (this.cfg.audio.muted) { + // Set mk.volume to -1 (setting to 0 wont work, so temp solution setting to -1) + this.mk.volume = -1; + } + // ipcRenderer.invoke('getStoreValue', 'audio.volume').then((value) => { + // self.mk.volume = value + // }) + + // load cached library + let librarySongs = await CiderCache.getCache("library-songs") + let libraryAlbums = await CiderCache.getCache("library-albums") + if (librarySongs) { + this.library.songs.listing = librarySongs + this.library.songs.displayListing = this.library.songs.listing + } + if (libraryAlbums) { + this.library.albums.listing = libraryAlbums + this.library.albums.displayListing = this.library.albums.listing + } + + window.onbeforeunload = function (e) { + window.localStorage.setItem("currentTrack", JSON.stringify(app.mk.nowPlayingItem)) + window.localStorage.setItem("currentTime", JSON.stringify(app.mk.currentPlaybackTime)) + window.localStorage.setItem("currentQueue", JSON.stringify(app.mk.queue.items)) + }; + + if (typeof MusicKit.PlaybackBitrate[app.cfg.audio.quality] !== "string") { + app.mk.bitrate = MusicKit.PlaybackBitrate[app.cfg.audio.quality] + } else { + app.mk.bitrate = 256 + app.cfg.audio.quality = "HIGH" + } + + switch (this.cfg.general.resumeOnStartupBehavior) { + default: + case "local": + // load last played track + try { + let lastItem = window.localStorage.getItem("currentTrack") + let time = window.localStorage.getItem("currentTime") + let queue = window.localStorage.getItem("currentQueue") + if (lastItem != null) { + lastItem = JSON.parse(lastItem) + let kind = lastItem.attributes.playParams.kind; + let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; + app.mk.setQueue({ + [truekind]: [lastItem.attributes.playParams.id], + parameters: { l: app.mklang } + }) + app.mk.mute() + setTimeout(() => { + app.mk.play().then(() => { + app.mk.pause().then(() => { + if (time != null) { + app.mk.seekToTime(time) + } + app.mk.unmute() + if (queue != null) { + queue = JSON.parse(queue) + if (queue && queue.length > 0) { + let ids = queue.map(e => (e.playParams ? e.playParams.id : (e.attributes.playParams ? e.attributes.playParams.id : ''))) + let i = 0; + if (ids.length > 0) { + for (let id of ids) { + if (!(i == 0 && ids[0] == lastItem.attributes.playParams.id)) { + try { + app.mk.playLater({ songs: [id] }) + } catch (err) { + } + } + i++; + } + } + } + } + + }) + + }) + }, 1500) + + } + + } catch (e) { + console.log(e) + } + break; + case "history": + let history = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, { l: app.mklang }) + if (history.data.data.length > 0) { + let lastItem = history.data.data[0] + let kind = lastItem.attributes.playParams.kind; + let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; + app.mk.setQueue({ + [truekind]: [lastItem.attributes.playParams.id], + parameters: { l: app.mklang } + }) + app.mk.mute() + setTimeout(() => { + app.mk.play().then(() => { + app.mk.pause().then(() => { + app.mk.unmute() + }) + }) + }, 1500) + } + + break; + case "disabled": + + break; + } + + MusicKit.getInstance().videoContainerElement = document.getElementById("apple-music-video-player") + + ipcRenderer.on('theme-update', (event, arg) => { + less.refresh(true, true, true) + self.setTheme(self.cfg.visual.theme, true) + }) + + ipcRenderer.on('SoundCheckTag', (event, tag) => { + // let replaygain = self.parseSCTagToRG(tag) + let soundcheck = tag.split(" ") + let numbers = [] + for (let item of soundcheck) { + numbers.push(parseInt(item, 16)) + + } + numbers.shift() + let peak = Math.max(numbers[6], numbers[7]) / 32768.0 + let gain = Math.pow(10, ((-7.63 - (Math.log10(peak) * 20)) / 20))// EBU R 128 Compliant + console.debug(`[Cider][MaikiwiSoundCheck] Peak Gain: ${Math.log10(peak) * 20} | Adjusting '${Math.log10(gain) * 20}' dB`) + try { + //CiderAudio.audioNodes.gainNode.gain.value = (Math.min(Math.pow(10, (replaygain.gain / 20)), (1 / replaygain.peak))) + CiderAudio.audioNodes.gainNode.gain.value = gain + } catch (e) { + } + }) + + ipcRenderer.on('play', function (_event, mode, id) { + if (mode !== 'url') { + self.mk.setQueue({ [mode]: id, parameters: { l: self.mklang } }).then(() => { + app.mk.play() + }) + + } else { + app.openAppleMusicURL(id) + } + }); + + this.mk.addEventListener(MusicKit.Events.playbackStateDidChange, () => { + ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes()); + }) + + this.mk.addEventListener(MusicKit.Events.playbackTimeDidChange, (a) => { + self.lyriccurrenttime = self.mk.currentPlaybackTime + this.currentSongInfo = a + self.playerLCD.playbackDuration = (self.mk.currentPlaybackTime) + // wsapi + ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes()); + }) + + this.mk.addEventListener(MusicKit.Events.nowPlayingItemDidChange, (a) => { + if (self.$refs.queue) { + self.$refs.queue.updateQueue(); + } + this.currentSongInfo = a + + + if (app.cfg.audio.normalization) { + // get unencrypted audio previews to get SoundCheck's normalization tag + try { + let previewURL = null + try { + previewURL = app.mk.nowPlayingItem.previewURL + } catch (e) { + } + if (previewURL == null && ((app.mk.nowPlayingItem?._songId ?? (app.mk.nowPlayingItem["songId"] ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id)) != -1)) { + app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/songs/${app.mk.nowPlayingItem?._songId ?? (app.mk.nowPlayingItem["songId"] ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id)}`).then((response) => { + previewURL = response.data.data[0].attributes.previews[0].url + if (previewURL) + ipcRenderer.send('getPreviewURL', previewURL) + }) + } else { + if (previewURL) + ipcRenderer.send('getPreviewURL', previewURL) + } + + } catch (e) { + } + } + + + try { + a = a.item.attributes; + } catch (_) { + } + let type = (self.mk.nowPlayingItem != null) ? self.mk.nowPlayingItem["type"] ?? '' : ''; + + if (type.includes("musicVideo") || type.includes("uploadedVideo") || type.includes("music-movie")) { + document.getElementById("apple-music-video-container").style.display = "block"; + // app.chrome.topChromeVisible = false + } else { + document.getElementById("apple-music-video-container").style.display = "none"; + // app.chrome.topChromeVisible = true + } + self.chrome.artworkReady = false + self.lyrics = [] + self.richlyrics = [] + app.getCurrentArtURL(); + // app.getNowPlayingArtwork(42); + app.getNowPlayingArtworkBG(32); + app.loadLyrics(); + + // Playback Notifications + if (this.cfg.general.playbackNotifications && !document.hasFocus() && a.artistName && a.artwork && a.name) { + if (this.notification) { + this.notification.close() + } + this.notification = new Notification(a.name, { + body: a.artistName, + icon: a.artwork.url.replace('/{w}x{h}bb', '/512x512bb').replace('/2000x2000bb', '/35x35bb'), + silent: true, + }); + } + + }) + + + this.mk.addEventListener(MusicKit.Events.playbackVolumeDidChange, (_a) => { + this.cfg.audio.volume = this.mk.volume + }) + + this.refreshPlaylists(this.isDev) + document.body.removeAttribute("loading") + if (window.location.hash != "") { + this.appRoute(window.location.hash) + } else { + this.page = "home" + } + + this.mediaKeyFixes() + + setTimeout(() => { + this.getSocialBadges() + this.getBrowsePage(); + this.$forceUpdate() + }, 500) + ipcRenderer.invoke("renderer-ready", true) + document.querySelector("#LOADER").remove() + if (this.cfg.general.themeUpdateNotification) { + this.checkForThemeUpdates() + } + }, + async checkForThemeUpdates() { + let self = this + const themes = ipcRenderer.sendSync("get-themes") + await asyncForEach(themes, async (theme) => { + if (theme.commit != "") { + await fetch(`https://api.github.com/repos/${theme.github_repo}/commits`) + .then(res => res.json()) + .then(res => { + if (res[0].sha != theme.commit) { + const notify = notyf.open({ className: "notyf-info", type: "info", message: `[Themes] ${theme.name} has an update available.` }) + notify.on("click", () => { + app.appRoute("themes-github") + notyf.dismiss(notify) + }) + } + }) + } + }) + }, + async setTheme(theme = "", onlyPrefs = false) { + console.log(theme) + if (this.cfg.visual.theme == "") { + this.cfg.visual.theme = "default.less" + } + if (theme == "") { + theme = this.cfg.visual.theme + } else { + this.cfg.visual.theme = "" + this.cfg.visual.theme = theme + } + const info = {} + try { + const infoResponse = await fetch("themes/" + app.cfg.visual.theme.replace("index.less", "theme.json")) + this.chrome.appliedTheme.info = await infoResponse.json() + } catch (e) { + e = null + console.warn("failed to get theme.json") + this.chrome.appliedTheme.info = {} + } + + + if (!onlyPrefs) { + document.querySelector("#userTheme").href = `themes/${this.cfg.visual.theme}` + document.querySelectorAll(`[id*='less']`).forEach(el => { + el.remove() + }); + less.refresh() + } + }, + getThemeDirective(directive = "") { + let directives = {} + if (typeof this.chrome.appliedTheme.info.directives == "object") { + directives = this.chrome.appliedTheme.info.directives + } + if (directives[directive]) { + return this.chrome.appliedTheme.info.directives[directive].value + } else if (this.cfg.visual.directives[directive]) { + return this.cfg.visual.directives.windowLayout + } else { + return "" + } + }, + unauthorize() { + bootbox.confirm(app.getLz('term.confirmLogout'), function (result) { + if (result) { + app.mk.unauthorize() + document.location.reload() + } + }); + }, + getAppClasses() { + let classes = {} + if (this.cfg.advanced.experiments.includes('compactui')) { + classes.compact = true + } + if (this.cfg.visual.window_background_style == "none") { + classes.simplebg = true + } + + if (this.getThemeDirective('windowLayout') == 'twopanel') { + classes.twopanel = true + } + return classes + }, + invokeDrawer(panel) { + if (this.drawer.panel == panel && this.drawer.open) { + if (panel == "lyrics") { + this.lyricon = false + } + this.drawer.panel = "" + this.drawer.open = false + } else { + if (panel == "lyrics") { + this.lyricon = true + } else { + this.lyricon = false + } + this.drawer.open = true + this.drawer.panel = panel + } + }, + select_removeMediaItem(id) { + this.selectedMediaItems.filter(item => item.guid == id).forEach(item => { + this.selectedMediaItems.splice(this.selectedMediaItems.indexOf(item), 1) + }) + }, + select_hasMediaItem(id) { + let found = this.selectedMediaItems.find(item => item.guid == id) + if (found) { + return true + } else { + return false + } + }, + select_selectMediaItem(id, kind, index, guid, library) { + if (!this.select_hasMediaItem(guid)) { + this.selectedMediaItems.push({ + id: id, + kind: kind, + index: index, + guid: guid, + isLibrary: library + }) + } + }, + getPlaylistFolderChildren(id) { + return this.playlists.listing.filter(playlist => { + if (playlist.parent == id) { + return playlist + } + }) + }, + async refreshPlaylists(localOnly = false, trackMap = true) { + let self = this + let newListing = [] + let trackMapping = {} + const cachedPlaylist = await CiderCache.getCache("library-playlists") + const cachedTrackMapping = await CiderCache.getCache("library-playlists-tracks") + + if (cachedPlaylist) { + console.log("using cached playlists") + this.playlists.listing = cachedPlaylist + self.sortPlaylists() + } else { + console.log("playlist has no cache") + } + + if(cachedTrackMapping) { + console.log("using cached track mapping") + this.playlists.trackMapping = cachedTrackMapping + } + if (localOnly) { + return + } + + this.library.backgroundNotification.message = "Building playlist cache..." + this.library.backgroundNotification.show = true + + async function deepScan(parent = "p.playlistsroot") { + console.log(`scanning ${parent}`) + const playlistData = await app.mk.api.v3.music(`/v1/me/library/playlist-folders/${parent}/children/`) + await asyncForEach(playlistData.data.data, async (playlist) => { + playlist.parent = parent + playlist.children = [] + playlist.tracks = [] + try { + if (trackMap) { + let tracks = await app.mk.api.v3.music(playlist.href + "/tracks").catch(e => { + // no tracks + e = null + }) + tracks.data.data.forEach(track => { + if (!trackMapping[track.id]) { + trackMapping[track.id] = [] + } + trackMapping[track.id].push(playlist.id) + + if (typeof track.attributes.playParams.catalogId == "string") { + if (!trackMapping[track.attributes.playParams.catalogId]) { + trackMapping[track.attributes.playParams.catalogId] = [] + } + trackMapping[track.attributes.playParams.catalogId].push(playlist.id) + } + }) + } + } catch (e) { } + if (playlist.type == "library-playlist-folders") { + try { + await deepScan(playlist.id).catch(e => { }) + } catch (e) { + + } + } + newListing.push(playlist) + }) + } + + await deepScan() + + this.library.backgroundNotification.show = false + this.playlists.listing = newListing + self.sortPlaylists() + if (trackMap) { + CiderCache.putCache("library-playlists-tracks", trackMapping) + this.playlists.trackMapping = trackMapping + } + CiderCache.putCache("library-playlists", newListing) + }, + sortPlaylists() { + this.playlists.listing.sort((a, b) => { + if (a.type === "library-playlist-folders" && b.type !== "library-playlist-folders") { + return -1 + } else if (a.type !== "library-playlist-folders" && b.type === "library-playlist-folders") { + return 1 + } else { + return 0 + } + }) + }, + playlistHeaderContextMenu(event) { + let menu = { + items: [{ + name: app.getLz('term.createNewPlaylist'), + action: () => { + this.newPlaylist() + } + }, + { + name: app.getLz('term.createNewPlaylistFolder'), + action: () => { + this.newPlaylistFolder() + } + } + ] + } + this.showMenuPanel(menu, event) + }, + async editPlaylistFolder(id, name = app.getLz('term.newPlaylist')) { + let self = this + this.mk.api.v3.music( + `/v1/me/library/playlist-folders/${id}`, {}, { + fetchOptions: { + method: "PATCH", + body: JSON.stringify({ + attributes: { name: name } + }) + } + } + ).then(res => { + self.refreshPlaylists() + }) + }, + async editPlaylist(id, name = app.getLz('term.newPlaylist')) { + let self = this + this.mk.api.v3.music( + `/v1/me/library/playlists/${id}`, {}, { + fetchOptions: { + method: "PATCH", + body: JSON.stringify({ + attributes: { name: name } + }) + } + } + ).then(res => { + self.refreshPlaylists() + }) + }, + copyToClipboard(str) { + if (navigator.userAgent.includes('Darwin') || navigator.appVersion.indexOf("Mac") != -1) { + this.darwinShare(str) + } else { + notyf.success(app.getLz('term.share.success')) + navigator.clipboard.writeText(str).then(r => console.log("Copied to clipboard.")) + } + }, + newPlaylist(name = app.getLz('term.newPlaylist'), tracks = []) { + let self = this + let request = { + name: name + } + if (tracks.length > 0) { + request.tracks = tracks + } + 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) + self.appRoute(`playlist_` + res.id); + self.showingPlaylist = []; + self.getPlaylistFromID(app.page.substring(9), true) + self.playlists.listing.push({ + id: res.id, + attributes: { + name: name + }, + parent: "p.playlistsroot" + }) + self.sortPlaylists() + setTimeout(() => { + app.refreshPlaylists() + }, 8000) + }) + }, + deletePlaylist(id) { + let self = this + if (confirm(app.getLz('term.deletePlaylist'))) { + app.mk.api.v3.music(`/v1/me/library/playlists/${id}`, {}, { + fetchOptions: { + method: "DELETE" + } + }).then(res => { + // remove this playlist from playlists.listing if it exists + let found = self.playlists.listing.find(item => item.id == id) + if (found) { + self.playlists.listing.splice(self.playlists.listing.indexOf(found), 1) + } + }) + } + }, + async showCollection(response, title, type, requestBody = {}) { + let self = this + console.log(response) + this.collectionList.requestBody = {} + this.collectionList.response = response + this.collectionList.title = title + this.collectionList.type = type + this.collectionList.requestBody = requestBody + app.appRoute("collection-list") + }, + async showArtistView(artist, title, view) { + let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${artist}/view/${view}?l=${this.mklang}`, {}, { includeResponseMeta: !0 })).data + console.log(response) + await this.showCollection(response, title, "artists") + }, + async showRecordLabelView(label, title, view) { + let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/record-labels/${label}/view/${view}?l=${this.mklang}`)).data + await this.showCollection(response, title, "record-labels") + }, + async showSearchView(term, group, title) { + + let requestBody = { + platform: "web", + groups: group, + types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels", + limit: 25, + relate: { + editorialItems: ["contents"] + }, + include: { + albums: ["artists"], + songs: ["artists"], + "music-videos": ["artists"] + }, + extend: "artistUrl", + fields: { + artists: "url,name,artwork,hero", + albums: "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url" + }, + with: "serverBubbles,lyricHighlights", + art: { + "url": "cf" + }, + omit: { + resource: ["autos"] + }, + l: this.mklang + } + 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) { + response = response.data.data[0] + let self = this + let playlistId = response.id + this.playlists.loadingState = (!transient) ? 0 : 1 + this.showingPlaylist = response + if (!response.relationships?.tracks?.next) { + this.playlists.loadingState = 1 + return + } + + function getPlaylistTracks(next) { + app.apiCall(app.musicBaseUrl + next, res => { + if (self.showingPlaylist.id != playlistId) { + return + } + self.showingPlaylist.relationships.tracks.data = self.showingPlaylist.relationships.tracks.data.concat(res.data) + if (res.next) { + getPlaylistTracks(res.next) + } else { + self.playlists.loadingState = 1 + } + }) + } + + getPlaylistTracks(response.relationships.tracks.next) + + }, + async getPlaylistFromID(id, transient = false) { + let self = this + const params = { + include: "tracks", + platform: "web", + "include[library-playlists]": "catalog,tracks", + "fields[playlists]": "curatorName,playlistType,name,artwork,url,playParams", + "include[library-songs]": "catalog,artists,albums,playParams,name,artwork,url", + "fields[catalog]": "artistUrl,albumUrl,url", + "fields[songs]": "artistUrl,albumUrl,playParams,name,artwork,url,artistName,albumName,durationInMillis", + l: this.mklang + } + if (!transient) { + this.playlists.loadingState = 0; + } + app.mk.api.v3.music(`/v1/me/library/playlists/${id}`, params).then(res => { + self.getPlaylistContinuous(res, transient) + }).catch((e) => { + console.log(e); + try { + app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${id}`, params).then(res => { + self.getPlaylistContinuous(res, transient) + }) + } catch (err) { + console.log(err) + } + }) + + }, + async getArtistFromID(id) { + this.page = "" + const artistData = await this.mkapi("artists", false, id, { + "views": "featured-release,full-albums,appears-on-albums,featured-albums,featured-on-albums,singles,compilation-albums,live-albums,latest-release,top-music-videos,similar-artists,top-songs,playlists,more-to-hear,more-to-see", + "extend": "artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero", + "extend[playlists]": "trackCount", + "include[songs]": "albums", + "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount", + "limit[artists:top-songs]": 20, + "art[url]": "f", + l: this.mklang + }, { includeResponseMeta: !0 }) + console.log(artistData.data.data[0]) + this.artistPage.data = artistData.data.data[0] + this.page = "artist-page" + }, + progressBarStyle() { + let val = this.playerLCD.playbackDuration + if (this.playerLCD.desiredDuration > 0) { + val = this.playerLCD.desiredDuration + } + let min = 0 + let max = this.mk.currentPlaybackDuration + let value = (val - min) / (max - min) * 100 + return { + 'background': ('linear-gradient(to right, var(--songProgressColor) 0%, var(--songProgressColor) ' + value + '%, var(--songProgressBackground) ' + value + '%, var(--songProgressBackground) 100%)') + } + }, + async getRecursive(response) { + // if response has a .next() property run it and keep running until .next is null or undefined + // and then return the response concatenated with the results of the next() call + function executeRequest() { + if (response.next) { + return response.next().then(executeRequest) + } else { + return response + } + } + + return executeRequest() + }, + async getRecursive2(response, sendTo) { + let returnData = { + "data": [], + "meta": {} + } + if (response.next) { + console.log("has next") + returnData.data.concat(response.data) + returnData.meta = response.meta + return await this.getRecursive(await response.next()) + } else { + console.log("no next") + returnData.data.concat(response.data) + return returnData + } + }, + async getSearchHints() { + if (this.search.term == "") { + this.search.hints = [] + return + } + 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 : [] + }, + getSongProgress() { + if (this.playerLCD.userInteraction) { + return this.playerLCD.desiredDuration + } else { + return this.playerLCD.playbackDuration + } + }, + /** + * Converts seconds to dd:hh:mm:ss + * @param time (in seconds) + * @param format (short, long) + * @returns {string} + */ + convertTime(time = 0, format = 'short') { + + if(isNaN(time)) { + time = 0 + } + if (typeof time !== "number") { + time = parseInt(time) + } + + const timeGates = { + 600: 15, // 10 Minutes + 3600: 14, // Hour + 36000: 12, // 10 Hours + } + + const datetime = new Date(time * 1000) + + let returnTime = datetime.toISOString().substring(11, 19); + for (let key in timeGates) { + if (time < key) { + returnTime = datetime.toISOString().substring(timeGates[key], 19) + break + } + } + + // Add the days on the front + let day; + if (time >= 86400) { + day = datetime.toISOString().substring(8, 10) + day = parseInt(day) - 1 + returnTime = day + ":" + returnTime + } + + if (format === 'long') { + const longFormat = [] + + // Seconds + if (datetime.getSeconds() !== 0) { + longFormat.push(`${datetime.getSeconds()} ${app.getLz('term.time.seconds')}`) + } + + // Minutes + if (time >= 60) { + longFormat.push(`${datetime.getMinutes()} ${app.getLz('term.time.minute', options = { count: datetime.getMinutes() })}`) + } + + // Hours + if (time >= 3600) { + longFormat.push(`${datetime.getHours()} ${app.getLz('term.time.hour', options = { count: datetime.getHours() })}`) + } + + // Days + if (time >= 86400) { + longFormat.push(`${day} ${app.getLz('term.time.day', options = { count: day })}`) + } + returnTime = longFormat.reverse().join(', ') + } + + return returnTime + }, + hashCode(str) { + let hash = 0, + i, chr; + if (str.length === 0) return hash; + for (i = 0; i < str.length; i++) { + chr = str.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; + }, + appRoute(route) { + if (route == "" || route == "#" || route == "/") { + return; + } + route = route.replace(/#/g, "") + // if the route contains does not include a / then route to the page directly + if (route.indexOf("/") == -1) { + this.page = route + window.location.hash = this.page + // if (this.page == "settings") { + // this.version + // } + return + } + let hash = route.split("/") + let page = hash[0] + let id = hash[1] + let isLibrary = hash[2] ?? false + this.routeView({ + kind: page, + id: id, + attributes: { + playParams: { kind: page, id: id, isLibrary: isLibrary } + } + }) + }, + routeView(item) { + let kind = (item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')) : (item.type ?? '')); + let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? '')); + ; + let isLibrary = item.attributes.playParams ? (item.attributes.playParams.isLibrary ?? false) : false; + if (kind.includes("playlist") || kind.includes("album")) { + app.showingPlaylist = []; + } + if (kind.toString().includes("apple-curator")) { + kind = "appleCurator" + app.getTypeFromID("appleCurator", (id), false, { + platform: "web", + include: "grouping,playlists", + extend: "editorialArtwork", + "art[url]": "f" + }); + window.location.hash = `${kind}/${id}` + document.querySelector("#app-content").scrollTop = 0 + } else if (kind.toString().includes("artist")) { + app.getArtistInfo(id, isLibrary) + window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ''}` + document.querySelector("#app-content").scrollTop = 0 + + } else if (kind.toString().includes("record-label") || kind.toString().includes("curator")) { + if (kind.toString().includes("record-label")) { + kind = "recordLabel" + } else { + kind = "curator" + } + app.page = (kind) + "_" + (id); + app.getTypeFromID((kind), (id), (isLibrary), { + extend: "editorialVideo", + include: 'grouping,playlists', + views: 'top-releases,latest-releases,top-artists' + }); + window.location.hash = `${kind}/${id}` + document.querySelector("#app-content").scrollTop = 0 + } else if (!kind.toString().includes("radioStation") && !kind.toString().includes("song") && !kind.toString().includes("musicVideo") && !kind.toString().includes("uploadedVideo") && !kind.toString().includes("music-movie")) { + let params = { + extend: "offers,editorialVideo", + "views": "appears-on,more-by-artist,related-videos,other-versions,you-might-also-like,video-extras,audio-extras", + } + if (kind.includes("playlist")) { + params["include"] = "tracks"; + } + if (kind.includes("album")) { + params["include[albums]"] = "artists" + params["fields[artists]"] = "name,url" + params["fields[albums]"] = "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,copyright" + } + + if (this.cfg.advanced.experiments.includes('inline-playlists')) { + let showModal = kind.toString().includes("album") || kind.toString().includes("playlist") + if (app.page.includes("playlist") || app.page.includes("album")) { + showModal = false + } + if (showModal) { + app.modals.showPlaylist = true + app.chrome.contentAreaScrolling = false + } else { + app.page = (kind) + "_" + (id); + window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ''}` + } + } else { + app.page = (kind) + "_" + (id); + window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ''}` + } + + + app.getTypeFromID((kind), (id), (isLibrary), params); + // document.querySelector("#app-content").scrollTop = 0 + } else { + app.playMediaItemById((id), (kind), (isLibrary), item.attributes.url ?? '') + } + }, + prevButton() { + if (!app.prevButtonBackIndicator && app.mk.nowPlayingItem && app.mk.currentPlaybackTime > 2) { + app.prevButtonBackIndicator = true; + try { + clearTimeout(app.pauseButtonTimer) + } catch (e) { + } + app.mk.seekToTime(0); + app.pauseButtonTimer = setTimeout(() => { + app.prevButtonBackIndicator = false + }, 3000); + } else { + try { + clearTimeout(app.pauseButtonTimer) + } catch (e) { + } + app.prevButtonBackIndicator = false; + app.skipToPreviousItem() + } + }, + async getNowPlayingItemDetailed(target) { + try { + 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", l: app.mklang }); + app.searchAndNavigate(u.data.data[0], target) + } catch (e) { + app.searchAndNavigate(app.mk.nowPlayingItem, target) + } + }, + async searchAndNavigate(item, target) { + let self = this + app.tmpVar = item; + switch (target) { + case "artist": + let artistId = ''; + try { + if (item.relationships.artists && item.relationships.artists.data.length > 0 && !item.relationships.artists.data[0].type.includes("library")) { + if (item.relationships.artists.data[0].type === "artist" || item.relationships.artists.data[0].type === "artists") { + artistId = item.relationships.artists.data[0].id + } + } + if (artistId == '') { + const url = (item.relationships.catalog.data[0].attributes.artistUrl); + artistId = (url).substring(url.lastIndexOf('/') + 1) + if (artistId.includes('viewCollaboration')) { + artistId = artistId.substring(artistId.lastIndexOf('ids=') + 4, artistId.lastIndexOf('-')) + } + } + } catch (_) { + } + + if (artistId == "") { + let artistQuery = (await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${item.attributes.artistName}`, { + limit: 1, + types: 'artists' + })).data.results; + try { + if (artistQuery.artists.data.length > 0) { + artistId = artistQuery.artists.data[0].id; + console.log(artistId) + } + } catch (e) { + } + } + console.log(artistId); + if (artistId != "") + self.appRoute(`artist/${artistId}`) + break; + case "album": + let albumId = ''; + try { + if ((item.type ?? item.playParams?.kind ?? "") == "albums") { + albumId = item.id ?? "" + } else if (item.relationships.albums && item.relationships.albums.data.length > 0 && !item.relationships.albums.data[0].type.includes("library")) { + if (item.relationships.albums.data[0].type === "album" || item.relationships.albums.data[0].type === "albums") { + albumId = item.relationships.albums.data[0].id + } + } + if (albumId == '') { + const url = (item.relationships.catalog.data[0].attributes.url); + albumId = (url).substring(url.lastIndexOf('/') + 1) + if (albumId.includes("?i=")) { + albumId = albumId.substring(0, albumId.indexOf("?i=")) + } + } + } catch (_) { + } + + if (albumId == "") { + try { + let albumQuery = (await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${(item.attributes.albumName ?? item.attributes.name ?? "") + " " + (item.attributes.artistName ?? "")}`, { + limit: 1, + types: 'albums' + })).data.results; + if (albumQuery.albums.data.length > 0) { + albumId = albumQuery.albums.data[0].id; + console.log(albumId) + } + } catch (e) { + } + } + if (albumId != "") { + self.appRoute(`album/${albumId}`) + } + break; + case "recordLabel": + let labelId = ''; + try { + labelId = item.relationships['record-labels'].data[0].id + } catch (_) { + } + + if (labelId == "") { + try { + let labelQuery = (await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${item.attributes.recordLabel}`, { + limit: 1, + types: 'record-labels' + })).data.results; + if (labelQuery["record-labels"].data.length > 0) { + labelId = labelQuery["record-labels"].data[0].id; + console.log(labelId) + } + } catch (e) { + } + } + if (labelId != "") { + app.showingPlaylist = [] + await app.getTypeFromID("recordLabel", labelId, false, { views: 'top-releases,latest-releases,top-artists' }); + app.page = "recordLabel_" + labelId; + } + + break; + } + }, + exitMV() { + MusicKit.getInstance().stop() + document.getElementById("apple-music-video-container").style.display = "none"; + }, + getArtistInfo(id, isLibrary) { + this.getArtistFromID(id) + //this.getTypeFromID("artist",id,isLibrary,query) + }, + followArtistById(id, follow) { + if (follow && !this.followingArtist(id)) { + this.cfg.home.followedArtists.push(id) + } else { + let index = this.cfg.home.followedArtists.indexOf(id) + if (index > -1) { + this.cfg.home.followedArtists.splice(index, 1) + } + } + }, + followingArtist(id) { + console.log(`check for ${id}`) + return this.cfg.home.followedArtists.includes(id) + }, + playMediaItem(item) { + let kind = (item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')) : (item.type ?? '')); + let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? '')); + ; + let isLibrary = item.attributes.playParams ? (item.attributes.playParams.isLibrary ?? false) : false; + let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; + // console.log(kind, id, isLibrary) + app.mk.stop().then(() => { + if (kind.includes("artist")) { + app.mk.setStationQueue({ artist: 'a-' + id }).then(() => { + app.mk.play() + }) + } + // else if (kind.includes("playlist") && (id.startsWith("p.") || id.startsWith("pl."))){ + // /* Randomize array in-place using Durstenfeld shuffle algorithm */ + // function shuffleArray(array) { + // for (var i = array.length - 1; i > 0; i--) { + // var j = Math.floor(Math.random() * (i + 1)); + // var temp = array[i]; + // array[i] = array[j]; + // array[j] = temp; + // } + // } + // app.mk.clearQueue().then(function () { { + // app.mk.setQueue({[truekind]: [item.attributes.playParams.id ?? item.id]}).then(function () { + // app.mk.play().then(function (){ + // app.mk.clearQueue().then(function (){ + // var playlistId = id + // const params = { + // include: "tracks", + // platform: "web", + // "include[library-playlists]": "catalog,tracks", + // "fields[playlists]": "curatorName,playlistType,name,artwork,url", + // "include[library-songs]": "catalog,artists,albums", + // "fields[catalog]": "artistUrl,albumUrl", + // "fields[songs]": "artistUrl,albumUrl" + // } + // var playlistId = '' + + // try { + // function getPlaylist(id, params, isLibrary){ + // if (isLibrary){ + // return app.mk.api.library.playlist(id, params) + // } else { return app.mk.api.playlist(id, params)} + // } + // getPlaylist(id, params, isLibrary).then(res => { + // let query = res.relationships.tracks.data.map(item => new MusicKit.MediaItem(item)); + // if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')} + // app.mk.queue.append(query) + // if (!res.relationships.tracks.next) { + // return + // } else { + // getPlaylistTracks(res.relationships.tracks.next) + // } + + // function getPlaylistTracks(next) { + // app.apiCall(app.musicBaseUrl + next, res => { + // if (res.id != playlistId) { + // return + // } + // let query = res.data.map(item => new MusicKit.MediaItem(item)) + // if (app.mk.shuffleMode == 1){shuffleArray(query); console.log('shf')} + // app.mk.queue.append(query) + + // if (res.next) { + // getPlaylistTracks(res.next) + // } + // }) + // } + // }) + // } catch (e) {} + + + // }) + // }) + // }) + // } + // }) + // } + else { + app.playMediaItemById((id), (kind), (isLibrary), item.attributes.url ?? '') + } + }) + }, + async getTypeFromID(kind, id, isLibrary = false, params = {}, params2 = {}) { + let a; + if (kind == "album" | kind == "albums") { + params["include"] = "tracks,artists,record-labels,catalog"; + } + params['l'] = this.mklang; + try { + a = await this.mkapi(kind.toString(), isLibrary, id.toString(), params, params2); + } catch (e) { + console.log(e); + try { + a = await this.mkapi(kind.toString(), !isLibrary, id.toString(), params, params2); + } catch (err) { + console.log(err); + a = [] + } finally { + if (kind == "appleCurator") { + app.appleCurator = a.data.data[0] + } else { + this.getPlaylistContinuous(a, true) + } + } + } finally { + if (kind == "appleCurator") { + app.appleCurator = a.data.data[0] + } else { + this.getPlaylistContinuous(a, true) + } + } + ; + }, + searchLibrarySongs() { + let self = this + let prefs = this.cfg.libraryPrefs.songs + let albumAdded = self.library?.albums?.listing?.map(function (i) { + return { [i.id]: i.attributes?.dateAdded } + }) + let startTime = new Date().getTime() + + function sortSongs() { + // sort this.library.songs.displayListing by song.attributes[self.library.songs.sorting] in descending or ascending order based on alphabetical order and numeric order + // check if song.attributes[self.library.songs.sorting] is a number and if so, sort by number if not, sort by alphabetical order ignoring case + self.library.songs.displayListing.sort((a, b) => { + let aa = a.attributes[prefs.sort] + let bb = b.attributes[prefs.sort] + if (prefs.sort == "genre") { + aa = a.attributes.genreNames[0] + bb = b.attributes.genreNames[0] + } + if (prefs.sort == "dateAdded") { + let albumida = a.relationships?.albums?.data[0]?.id ?? '1970-01-01T00:01:01Z' + let albumidb = b.relationships?.albums?.data[0]?.id ?? '1970-01-01T00:01:01Z' + aa = startTime - new Date(((albumAdded.find(i => i[albumida])) ?? [])[albumida] ?? '1970-01-01T00:01:01Z').getTime() + bb = startTime - new Date(((albumAdded.find(i => i[albumidb])) ?? [])[albumidb] ?? '1970-01-01T00:01:01Z').getTime() + } + if (aa == null) { + aa = "" + } + if (bb == null) { + bb = "" + } + if (prefs.sortOrder == "asc") { + if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { + return aa - bb + } else { + return aa.toString().toLowerCase().localeCompare(bb.toString().toLowerCase()) + } + } else if (prefs.sortOrder == "desc") { + if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { + return bb - aa + } else { + return bb.toString().toLowerCase().localeCompare(aa.toString().toLowerCase()) + } + } + }) + } + + if (this.library.songs.search == "") { + this.library.songs.displayListing = this.library.songs.listing + sortSongs() + } else { + this.library.songs.displayListing = this.library.songs.listing.filter(item => { + let itemName = item.attributes.name.toLowerCase() + let searchTerm = this.library.songs.search.toLowerCase() + let artistName = "" + let albumName = "" + if (item.attributes.artistName != null) { + artistName = item.attributes.artistName.toLowerCase() + } + if (item.attributes.albumName != null) { + albumName = item.attributes.albumName.toLowerCase() + } + + // remove any non-alphanumeric characters and spaces from search term and item name + searchTerm = searchTerm.replace(/[^a-z0-9 ]/gi, "") + itemName = itemName.replace(/[^a-z0-9 ]/gi, "") + artistName = artistName.replace(/[^a-z0-9 ]/gi, "") + albumName = albumName.replace(/[^a-z0-9 ]/gi, "") + + if (itemName.includes(searchTerm) || artistName.includes(searchTerm) || albumName.includes(searchTerm)) { + return item + } + }) + sortSongs() + } + }, + getAlbumSort() { + this.library.albums.sortOrder[1] = this.cfg.libraryPrefs.albums.sortOrder; + this.library.albums.sorting[1] = this.cfg.libraryPrefs.albums.sort; + }, + // make a copy of searchLibrarySongs except use Albums instead of Songs + searchLibraryAlbums(index) { + let self = this + + function sortAlbums() { + // sort this.library.albums.displayListing by album.attributes[self.library.albums.sorting[index]] in descending or ascending order based on alphabetical order and numeric order + // check if album.attributes[self.library.albums.sorting[index]] is a number and if so, sort by number if not, sort by alphabetical order ignoring case + self.library.albums.displayListing.sort((a, b) => { + let aa = a.attributes[self.library.albums.sorting[index]] + let bb = b.attributes[self.library.albums.sorting[index]] + if (self.library.albums.sorting[index] == "genre") { + aa = a.attributes.genreNames[0] + bb = b.attributes.genreNames[0] + } + if (aa == null) { + aa = "" + } + if (bb == null) { + bb = "" + } + if (self.library.albums.sortOrder[index] == "asc") { + if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { + return aa - bb + } else { + return aa.toString().toLowerCase().localeCompare(bb.toString().toLowerCase()) + } + } else if (self.library.albums.sortOrder[index] == "desc") { + if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { + return bb - aa + } else { + return bb.toString().toLowerCase().localeCompare(aa.toString().toLowerCase()) + } + } + }) + } + + if (this.library.albums.search == "") { + this.library.albums.displayListing = this.library.albums.listing + sortAlbums() + } else { + this.library.albums.displayListing = this.library.albums.listing.filter(item => { + let itemName = item.attributes.name.toLowerCase() + let searchTerm = this.library.albums.search.toLowerCase() + let artistName = "" + let albumName = "" + if (item.attributes.artistName != null) { + artistName = item.attributes.artistName.toLowerCase() + } + if (item.attributes.albumName != null) { + albumName = item.attributes.albumName.toLowerCase() + } + + // remove any non-alphanumeric characters and spaces from search term and item name + searchTerm = searchTerm.replace(/[^a-z0-9 ]/gi, "") + itemName = itemName.replace(/[^a-z0-9 ]/gi, "") + artistName = artistName.replace(/[^a-z0-9 ]/gi, "") + albumName = albumName.replace(/[^a-z0-9 ]/gi, "") + + if (itemName.includes(searchTerm) || artistName.includes(searchTerm) || albumName.includes(searchTerm)) { + return item + } + }) + sortAlbums() + } + }, + // make a copy of searchLibrarySongs except use Albums instead of Songs + searchLibraryArtists(index) { + let self = this + + function sortArtists() { + // sort this.library.albums.displayListing by album.attributes[self.library.albums.sorting[index]] in descending or ascending order based on alphabetical order and numeric order + // check if album.attributes[self.library.albums.sorting[index]] is a number and if so, sort by number if not, sort by alphabetical order ignoring case + self.library.artists.displayListing.sort((a, b) => { + let aa = a.attributes[self.library.artists.sorting[index]] + let bb = b.attributes[self.library.artists.sorting[index]] + if (self.library.artists.sorting[index] == "genre") { + aa = a.attributes.genreNames[0] + bb = b.attributes.genreNames[0] + } + if (aa == null) { + aa = "" + } + if (bb == null) { + bb = "" + } + if (self.library.artists.sortOrder[index] == "asc") { + if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { + return aa - bb + } else { + return aa.toString().toLowerCase().localeCompare(bb.toString().toLowerCase()) + } + } else if (self.library.artists.sortOrder[index] == "desc") { + if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { + return bb - aa + } else { + return bb.toString().toLowerCase().localeCompare(aa.toString().toLowerCase()) + } + } + }) + } + + if (this.library.artists.search == "") { + this.library.artists.displayListing = this.library.artists.listing + sortArtists() + } else { + this.library.artists.displayListing = this.library.artists.listing.filter(item => { + let itemName = item.attributes.name.toLowerCase() + let searchTerm = this.library.artists.search.toLowerCase() + let artistName = "" + let albumName = "" + // if (item.attributes.artistName != null) { + // artistName = item.attributes.artistName.toLowerCase() + // } + // if (item.attributes.albumName != null) { + // albumName = item.attributes.albumName.toLowerCase() + // } + + // remove any non-alphanumeric characters and spaces from search term and item name + searchTerm = searchTerm.replace(/[^a-z0-9 ]/gi, "") + itemName = itemName.replace(/[^a-z0-9 ]/gi, "") + + + if (itemName.includes(searchTerm) || artistName.includes(searchTerm) || albumName.includes(searchTerm)) { + return item + } + }) + sortArtists() + } + }, + getSidebarItemClass(page) { + if (this.page == page) { + return ["active"] + } else { + return [] + } + }, + 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) { + return + } + let truemethod = (!method.endsWith("s")) ? (method + "s") : method; + try { + if (library) { + return await this.mk.api.v3.music(`v1/me/library/${truemethod}/${term.toString()}`, params, params2) + } else { + return await this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/${truemethod}/${term.toString()}`, params, params2) + } + } catch (e) { + console.log(e) + return await this.mkapi(method, library, term, params, params2, attempts + 1) + } + }, + getLibraryGenres() { + let genres = [] + genres = [] + this.library.songs.listing.forEach((item) => { + item.attributes.genreNames.forEach((genre) => { + if (!genres.includes(genre)) { + genres.push(genre) + } + }) + }) + return genres + }, + async getLibrarySongsFull(force = false) { + let self = this + let library = [] + let cacheId = "library-songs" + let downloaded = null; + if ((this.library.songs.downloadState == 2) && !force) { + return + } + if (this.library.songs.downloadState == 1) { + return + } + let librarySongs = await CiderCache.getCache(cacheId) + if (librarySongs) { + this.library.songs.listing = librarySongs + this.searchLibrarySongs() + } + if (this.songstest) { + return + } + this.library.songs.downloadState = 1 + this.library.backgroundNotification.show = true + this.library.backgroundNotification.message = app.getLz('notification.updatingLibrarySongs') + + function downloadChunk() { + const params = { + "include[library-songs]": "catalog,artists,albums", + "fields[artists]": "name,url,id", + "fields[albums]": "name,url,id", + platform: "web", + "fields[catalog]": "artistUrl,albumUrl", + "fields[songs]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", + limit: 100, + l: self.mklang + } + const safeparams = { + "platform": "web", + "limit": 80 + } + self.library.songs.downloadState = 1 + if (downloaded == null) { + app.mk.api.v3.music(`/v1/me/library/songs/`, params).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading'); + app.mk.api.v3.music(`/v1/me/library/songs/`, safeparams).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading failed', error) + app.library.songs.downloadState = 2 + app.library.backgroundNotification.show = false + }) + }) + } else { + if (downloaded.next != null) { + app.mk.api.v3.music(downloaded.next, params).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading'); + app.mk.api.v3.music(downloaded.next, safeparams).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading failed', error) + app.library.songs.downloadState = 2 + app.library.backgroundNotification.show = false + }) + }) + } else { + console.log("Download next", downloaded.next) + } + } + } + + function processChunk(response) { + downloaded = response + library = library.concat(downloaded.data) + self.library.backgroundNotification.show = true + self.library.backgroundNotification.message = app.getLz('notification.updatingLibrarySongs') + self.library.backgroundNotification.total = downloaded.meta.total + self.library.backgroundNotification.progress = library.length + + if (downloaded.meta.total == 0) { + self.library.songs.downloadState = 3 + return + } + if (typeof downloaded.next == "undefined") { + console.log("downloaded.next is undefined") + self.library.songs.listing = library + self.library.songs.downloadState = 2 + self.library.backgroundNotification.show = false + self.searchLibrarySongs() + CiderCache.putCache(cacheId, library) + } + if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { + console.log(`downloading next chunk - ${library.length} songs so far`) + downloadChunk() + } else { + self.library.songs.listing = library + self.library.songs.downloadState = 2 + self.library.backgroundNotification.show = false + self.searchLibrarySongs() + CiderCache.putCache(cacheId, library) + // console.log(library) + } + } + + downloadChunk() + }, + // copy the getLibrarySongsFull function except change Songs to Albums + async getLibraryAlbumsFull(force = false, index) { + let self = this + let library = [] + let cacheId = "library-albums" + let downloaded = null; + if ((this.library.albums.downloadState == 2 || this.library.albums.downloadState == 1) && !force) { + return + } + let libraryAlbums = await CiderCache.getCache(cacheId) + if (libraryAlbums) { + this.library.albums.listing = libraryAlbums + this.searchLibraryAlbums(index) + } + if (this.songstest) { + return + } + this.library.albums.downloadState = 1 + this.library.backgroundNotification.show = true + this.library.backgroundNotification.message = app.getLz('notification.updatingLibraryAlbums') + + function downloadChunk() { + self.library.albums.downloadState = 1 + const params = { + "include[library-albums]": "catalog,artists,albums", + "fields[artists]": "name,url,id", + // "fields[albums]": "name,url,id", + platform: "web", + "fields[catalog]": "artistUrl,albumUrl", + "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", + limit: 100, + l: self.mklang + } + const safeparams = { + platform: "web", + limit: "60", + "include[library-albums]": "artists", + "include[library-artists]": "catalog", + "include[albums]": "artists", + "fields[artists]": "name,url", + "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", + "includeOnly": "catalog,artists" + } + if (downloaded == null) { + app.mk.api.v3.music(`/v1/me/library/albums/`, params).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading'); + app.mk.api.v3.music(`/v1/me/library/albums/`, safeparams).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading failed', error) + app.library.albums.downloadState = 2 + app.library.backgroundNotification.show = false + }) + }) + } else { + if (downloaded.next != null) { + app.mk.api.v3.music(downloaded.next, params).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading'); + app.mk.api.v3.music(downloaded.next, safeparams).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading failed', error); + app.library.albums.downloadState = 2 + app.library.backgroundNotification.show = false + }) + }) + } else { + console.log("Download next", downloaded.next) + } + } + } + + function processChunk(response) { + downloaded = response + library = library.concat(downloaded.data) + self.library.backgroundNotification.show = true + self.library.backgroundNotification.message = app.getLz('notification.updatingLibraryAlbums') + self.library.backgroundNotification.total = downloaded.meta.total + self.library.backgroundNotification.progress = library.length + if (downloaded.meta.total == 0) { + self.library.albums.downloadState = 3 + return + } + if (typeof downloaded.next == "undefined") { + console.log("downloaded.next is undefined") + self.library.albums.listing = library + self.library.albums.downloadState = 2 + self.library.backgroundNotification.show = false + CiderCache.putCache(cacheId, library) + self.searchLibraryAlbums(index) + } + if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { + console.log(`downloading next chunk - ${library.length + } albums so far`) + downloadChunk() + } else { + self.library.albums.listing = library + self.library.albums.downloadState = 2 + self.library.backgroundNotification.show = false + CiderCache.putCache(cacheId, library) + self.searchLibraryAlbums(index) + // console.log(library) + } + } + + downloadChunk() + }, + // copy the getLibrarySongsFull function except change Songs to Albums + async getLibraryArtistsFull(force = false, index) { + let self = this + let library = [] + let cacheId = "library-artists" + let downloaded = null; + if ((this.library.artists.downloadState == 2 || this.library.artists.downloadState == 1) && !force) { + return + } + let libraryArtists = await CiderCache.getCache(cacheId) + if (libraryArtists) { + this.library.artists.listing = libraryArtists + this.searchLibraryArtists(index) + } + if (this.songstest) { + return + } + this.library.artists.downloadState = 1 + this.library.backgroundNotification.show = true + this.library.backgroundNotification.message = app.getLz('notification.updatingLibraryArtists') + + function downloadChunk() { + self.library.artists.downloadState = 1 + const params = { + include: "catalog", + // "include[library-artists]": "catalog,artists,albums", + // "fields[artists]": "name,url,id", + // "fields[albums]": "name,url,id", + platform: "web", + // "fields[catalog]": "artistUrl,albumUrl", + // "fields[artists]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", + limit: 100, + l: self.mklang + } + const safeparams = { + include: "catalog", + platform: "web", + limit: 50, + } + if (downloaded == null) { + app.mk.api.v3.music(`/v1/me/library/artists/`, params).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading'); + app.mk.api.v3.music(`/v1/me/library/artists/`, safeparams).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading failed', error) + app.library.artists.downloadState = 2 + app.library.backgroundNotification.show = false + }) + }) + + } else { + if (downloaded.next != null) { + app.mk.api.v3.music(downloaded.next, params).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading'); + app.mk.api.v3.music(downloaded.next, safeparams).then((response) => { + processChunk(response.data) + }).catch((error) => { + console.log('safe loading failed', error) + app.library.artists.downloadState = 2 + app.library.backgroundNotification.show = false + }) + }) + } else { + console.log("Download next", downloaded.next) + } + + } + } + + function processChunk(response) { + downloaded = response + library = library.concat(downloaded.data) + self.library.backgroundNotification.show = true + self.library.backgroundNotification.message = app.getLz('notification.updatingLibraryArtists') + self.library.backgroundNotification.total = downloaded.meta.total + self.library.backgroundNotification.progress = library.length + if (downloaded.meta.total == 0) { + self.library.albums.downloadState = 3 + return + } + if (typeof downloaded.next == "undefined") { + console.log("downloaded.next is undefined") + self.library.artists.listing = library + self.library.artists.downloadState = 2 + self.library.artists.show = false + CiderCache.putCache(cacheId, library) + self.searchLibraryArtists(index) + } + if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { + console.log(`downloading next chunk - ${library.length + } artists so far`) + downloadChunk() + } else { + self.library.artists.listing = library + self.library.artists.downloadState = 2 + self.library.backgroundNotification.show = false + CiderCache.putCache(cacheId, library) + self.searchLibraryArtists(index) + // console.log(library) + } + } + + downloadChunk() + }, + getTotalTime() { + try { + if (app.showingPlaylist.relationships.tracks.data.length > 0) { + const timeInSeconds = Math.round([].concat(...app.showingPlaylist.relationships.tracks.data).reduce((a, { attributes: { durationInMillis } }) => a + durationInMillis, 0) / 1000); + return `${app.showingPlaylist.relationships.tracks.data.length} ${app.getLz('term.track', options = { count: app.showingPlaylist.relationships.tracks.data.length })}, ${this.convertTime(timeInSeconds, 'long')}` + } else return "" + } catch (err) { + return "" + } + }, + async getLibrarySongs() { + let response = await this.mkapi("songs", true, "", { limit: 100, l: this.mklang }, { includeResponseMeta: !0 }) + this.library.songs.listing = response.data.data + this.library.songs.meta = response.data.meta + }, + async getLibraryAlbums() { + let response = await this.mkapi("albums", true, "", { limit: 100, l: this.mklang }, { includeResponseMeta: !0 }) + this.library.albums.listing = response.data.data + this.library.albums.meta = response.data.meta + }, + async getListenNow(attempt = 0) { + if (this.listennow.timestamp > Date.now() - 120000) { + return + } + + if (attempt > 3) { + return + } + try { + this.listennow = (await this.mk.api.v3.music(`v1/me/recommendations?timezone=${encodeURIComponent(this.formatTimezoneOffset())}`, { + name: "listen-now", + with: "friendsMix,library,social", + "art[social-profiles:url]": "c", + "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", + l: this.mklang + }, { + includeResponseMeta: !0, + reload: !0 + })).data; + this.listennow.timestamp = Date.now() + console.log(this.listennow) + } catch (e) { + console.log(e) + this.getListenNow(attempt + 1) + } + }, + async getBrowsePage(attempt = 0) { + if (this.browsepage.timestamp > Date.now() - 120000) { + return + } + if (attempt > 3) { + return + } + try { + let browse = await app.mk.api.v3.music(`/v1/editorial/${app.mk.storefrontId}/groupings`, { + platform: "web", + name: "music", + "omit[resource:artists]": "relationships", + "include[albums]": "artists", + "include[songs]": "artists", + "include[music-videos]": "artists", + extend: "editorialArtwork,artistUrl", + "fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes", + "art[url]": "f", + l: this.mklang + }); + this.browsepage = browse.data.data[0]; + this.browsepage.timestamp = Date.now() + console.log(this.browsepage) + } catch (e) { + console.log(e) + this.getBrowsePage(attempt + 1) + } + }, + async getRadioStations(attempt = 0) { + if (attempt > 3) { + return + } + try { + this.radio.personal = (await app.mk.api.v3.music(`/v1/me/recent/radio-stations`, { + "platform": "web", + "art[url]": "f", + l: this.mklang + })).data.data; + } catch (e) { + console.log(e) + this.getRadioStations(attempt + 1) + } + }, + async getMadeForYou(attempt = 0) { + if (attempt > 3) { + return + } + try { + let mfu = await app.mk.api.v3.music("/v1/me/library/playlists?platform=web&extend=editorialVideo&fields%5Bplaylists%5D=lastModifiedDate&filter%5Bfeatured%5D=made-for-you&include%5Blibrary-playlists%5D=catalog&fields%5Blibrary-playlists%5D=artwork%2Cname%2CplayParams%2CdateAdded") + this.madeforyou = mfu.data + } catch (e) { + console.log(e) + this.getMadeForYou(attempt + 1) + } + }, + newPlaylistFolder(name = app.getLz('term.newPlaylistFolder')) { + let self = this + this.mk.api.v3.music( + "/v1/me/library/playlist-folders/", {}, { + fetchOptions: { + method: "POST", + body: JSON.stringify({ + attributes: { name: name } + }) + } + } + ).then((res) => { + let playlist = (res.data.data[0]) + self.playlists.listing.push({ + id: playlist.id, + attributes: { + name: playlist.attributes.name + }, + type: "library-playlist-folders", + parent: "p.playlistsroot" + }) + self.sortPlaylists() + setTimeout(() => { + app.refreshPlaylists() + }, 13000) + }) + }, + showSearch() { + this.page = "search" + }, + loadLyrics() { + const musicType = (MusicKit.getInstance().nowPlayingItem != null) ? MusicKit.getInstance().nowPlayingItem["type"] ?? '' : ''; + // console.log("mt", musicType) + if (musicType === "musicVideo") { + this.loadYTLyrics(); + } else { + if (app.cfg.lyrics.enable_mxm) { + this.loadMXM(); + } else { + this.loadAMLyrics(); + } + } + }, + loadAMLyrics() { + const songID = (this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem["_songId"] ?? (this.mk.nowPlayingItem["songId"] ?? -1) : -1; + // this.getMXM( trackName, artistName, 'en', duration); + if (songID != -1) { + this.mk.api.v3.music(`v1/catalog/${this.mk.storefrontId}/songs/${songID}/lyrics`) + .then((response) => { + this.lyricsMediaItem = response.data?.data[0]?.attributes["ttml"] + this.parseTTML() + }) + } + }, + addToLibrary(id) { + let self = this + this.mk.addToLibrary(id).then((data) => { + self.getLibrarySongsFull(true) + }) + notyf.success(app.getLz('action.addToLibrary.success')); + }, + removeFromLibrary(kind, id) { + let self = this + let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; + app.mk.api.v3.music(`v1/me/library/${truekind}/${id.toString()}`, {}, { + fetchOptions: { + method: "DELETE" + } + }).then((data) => { + self.getLibrarySongsFull(true) + }) + notyf.success(app.getLz('action.removeFromLibrary.success')) + }, + async loadYTLyrics() { + const track = (this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.title ?? '' : ''; + const artist = (this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.artistName ?? '' : ''; + const time = (this.mk.nowPlayingItem != null) ? (Math.round((this.mk.nowPlayingItem.attributes["durationInMillis"] ?? -1000) / 1000) ?? -1) : -1; + ipcRenderer.invoke('getYTLyrics', track, artist).then((result) => { + if (result.length > 0) { + let ytid = result[0]['id']['videoId']; + if (app.cfg.lyrics.enable_yt) { + loadYT(ytid, app.cfg.lyrics.mxm_language ?? "en") + } else { + app.loadMXM() + } + } else { + app.loadMXM() + } + + function loadYT(id, lang) { + let req = new XMLHttpRequest(); + let url = `https://www.youtube.com/watch?&v=${id}`; + req.open('GET', url, true); + req.onerror = function (e) { + this.loadMXM(); + } + req.onload = function () { + // console.log(this.responseText); + let res = this.responseText; + let captionurl1 = res.substring(res.indexOf(`{"playerCaptionsRenderer":{"baseUrl":"`) + (`{"playerCaptionsRenderer":{"baseUrl":"`).length); + let captionurl = captionurl1.substring(0, captionurl1.indexOf(`"`)); + if (captionurl.includes("timedtext")) { + let json = JSON.parse(`{"url": "${captionurl}"}`); + let newurl = json.url + `&lang=${lang}&format=ttml` + + let req2 = new XMLHttpRequest(); + + req2.open('GET', newurl, true); + req2.onerror = function (e) { + app.loadMXM(); + } + req2.onload = function () { + try { + const ttmlLyrics = this.responseText; + if (ttmlLyrics) { + this.lyricsMediaItem = ttmlLyrics + this.parseTTML() + } + } catch (e) { + app.loadMXM(); + } + + } + req2.send(); + } else { + + app.loadMXM(); + + } + } + req.send(); + } + + }) + + }, + loadMXM() { + let attempt = 0; + const track = encodeURIComponent((this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.title ?? '' : ''); + const artist = encodeURIComponent((this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.artistName ?? '' : ''); + const time = encodeURIComponent((this.mk.nowPlayingItem != null) ? (Math.round((this.mk.nowPlayingItem.attributes["durationInMillis"] ?? -1000) / 1000) ?? -1) : -1); + const id = encodeURIComponent((this.mk.nowPlayingItem != null) ? app.mk.nowPlayingItem._songId ?? (app.mk.nowPlayingItem["songId"] ?? '') : ''); + let lrcfile = ""; + let richsync = []; + const lang = app.cfg.lyrics.mxm_language // translation language + function revisedRandId() { + return Math.random().toString(36).replace(/[^a-z]+/g, '').slice(2, 10); + } + + /* get token */ + function getToken(mode, track, artist, songid, lang, time, id) { + if (attempt > 2) { + app.loadAMLyrics(); + } else { + attempt = attempt + 1; + let url = "https://apic-desktop.musixmatch.com/ws/1.1/token.get?app_id=web-desktop-app-v1.0&t=" + revisedRandId(); + let req = new XMLHttpRequest(); + req.overrideMimeType("application/json"); + req.open('GET', url, true); + req.setRequestHeader("authority", "apic-desktop.musixmatch.com"); + req.onload = function () { + let jsonResponse = JSON.parse(this.responseText); + let status2 = jsonResponse["message"]["header"]["status_code"]; + if (status2 == 200) { + let token = jsonResponse["message"]["body"]["user_token"] ?? ''; + if (token != "" && token != "UpgradeOnlyUpgradeOnlyUpgradeOnlyUpgradeOnly") { + console.log('200 token', mode); + // token good + app.mxmtoken = token; + + if (mode == 1) { + getMXMSubs(track, artist, app.mxmtoken, lang, time, id); + } else { + getMXMTrans(songid, lang, app.mxmtoken); + } + } else { + console.log('fake 200 token'); + getToken(mode, track, artist, songid, lang, time) + } + } else { + // console.log('token 4xx'); + getToken(mode, track, artist, songid, lang, time) + } + + }; + req.onerror = function () { + console.log('error'); + app.loadAMLyrics(); + }; + req.send(); + } + } + + function getMXMSubs(track, artist, token, lang, time, id) { + let usertoken = encodeURIComponent(token); + let richsyncQuery = (app.cfg.lyrics.mxm_karaoke) ? "&optional_calls=track.richsync" : "" + let timecustom = (!time || (time && time < 0)) ? '' : `&f_subtitle_length=${time}&q_duration=${time}&f_subtitle_length_max_deviation=40`; + let itunesid = (id && id != "") ? `&track_itunes_id=${id}` : ''; + let url = "https://apic-desktop.musixmatch.com/ws/1.1/macro.subtitles.get?format=json&namespace=lyrics_richsynched" + richsyncQuery + "&subtitle_format=lrc&q_artist=" + artist + "&q_track=" + track + itunesid + "&usertoken=" + usertoken + timecustom + "&app_id=web-desktop-app-v1.0&t=" + revisedRandId(); + let req = new XMLHttpRequest(); + req.overrideMimeType("application/json"); + req.open('GET', url, true); + req.setRequestHeader("authority", "apic-desktop.musixmatch.com"); + req.onload = function () { + let jsonResponse = JSON.parse(this.responseText); + console.log(jsonResponse); + let status1 = jsonResponse["message"]["header"]["status_code"]; + + if (status1 == 200) { + let id = ''; + try { + if (jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["header"]["status_code"] == 200 && jsonResponse["message"]["body"]["macro_calls"]["track.subtitles.get"]["message"]["header"]["status_code"] == 200) { + id = jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["body"]["track"]["track_id"] ?? ''; + lrcfile = jsonResponse["message"]["body"]["macro_calls"]["track.subtitles.get"]["message"]["body"]["subtitle_list"][0]["subtitle"]["subtitle_body"]; + + try { + let lrcrich = jsonResponse["message"]["body"]["macro_calls"]["track.richsync.get"]["message"]["body"]["richsync"]["richsync_body"]; + richsync = JSON.parse(lrcrich); + app.richlyrics = richsync; + } catch (_) { + } + } + + if (lrcfile == "") { + app.loadAMLyrics() + } else { + if (richsync == [] || richsync.length == 0) { + console.log("ok"); + // process lrcfile to json here + app.lyricsMediaItem = lrcfile + let u = app.lyricsMediaItem.split(/[\r\n]/); + let preLrc = [] + for (var i = u.length - 1; i >= 0; i--) { + let xline = (/(\[[0-9.:\[\]]*\])+(.*)/).exec(u[i]) + let end = (preLrc.length > 0) ? ((preLrc[preLrc.length - 1].startTime) ?? 99999) : 99999 + preLrc.push({ + startTime: app.toMS(xline[1].substring(1, xline[1].length - 2)) ?? 0, + endTime: end, + line: xline[2], + translation: '' + }) + } + if (preLrc.length > 0) + preLrc.push({ + startTime: 0, + endTime: preLrc[preLrc.length - 1].startTime, + line: "lrcInstrumental", + translation: '' + }); + app.lyrics = preLrc.reverse(); + } else { + let preLrc = richsync.map(function (item) { + return { + startTime: item.ts, + endTime: item.te, + line: item.x, + translation: '' + } + }) + if (preLrc.length > 0) + preLrc.unshift({ + startTime: 0, + endTime: preLrc[0].startTime, + line: "lrcInstrumental", + translation: '' + }); + app.lyrics = preLrc; + } + if (lrcfile != null && lrcfile != '' && lang != "disabled") { + // load translation + getMXMTrans(id, lang, token); + } else { + app.loadAMLyrics() + } + } + } catch (e) { + console.log(e); + app.loadAMLyrics() + } + } else { //4xx rejected + getToken(1, track, artist, '', lang, time); + } + } + req.onerror = function () { + console.log('error'); + app.loadAMLyrics(); + }; + req.send(); + } + + function getMXMTrans(id, lang, token) { + if (lang != "disabled" && id != '') { + let usertoken = encodeURIComponent(token); + let url2 = "https://apic-desktop.musixmatch.com/ws/1.1/crowd.track.translations.get?translation_fields_set=minimal&selected_language=" + lang + "&track_id=" + id + "&comment_format=text&part=user&format=json&usertoken=" + usertoken + "&app_id=web-desktop-app-v1.0&t=" + revisedRandId(); + let req2 = new XMLHttpRequest(); + req2.overrideMimeType("application/json"); + req2.open('GET', url2, true); + req2.setRequestHeader("authority", "apic-desktop.musixmatch.com"); + req2.onload = function () { + let jsonResponse2 = JSON.parse(this.responseText); + console.log(jsonResponse2); + let status2 = jsonResponse2["message"]["header"]["status_code"]; + if (status2 == 200) { + try { + let preTrans = [] + let u = app.lyrics; + let translation_list = jsonResponse2["message"]["body"]["translations_list"]; + if (translation_list.length > 0) { + for (var i = 0; i < u.length - 1; i++) { + preTrans[i] = "" + for (var trans_line of translation_list) { + if (u[i].line == " " + trans_line["translation"]["matched_line"] || u[i].line == trans_line["translation"]["matched_line"]) { + u[i].translation = trans_line["translation"]["description"]; + break; + } + } + } + app.lyrics = u; + } + } catch (e) { + /// not found trans -> ignore + } + } else { //4xx rejected + getToken(2, '', '', id, lang, ''); + } + } + req2.send(); + } + + } + + if (track != "" & track != "No Title Found") { + if (app.mxmtoken != null && app.mxmtoken != '') { + getMXMSubs(track, artist, app.mxmtoken, lang, time, id) + } else { + getToken(1, track, artist, '', lang, time); + } + } + }, + toMS(str) { + let rawTime = str.match(/(\d+:)?(\d+:)?(\d+)(\.\d+)?/); + let hours = (rawTime[2] != null) ? (rawTime[1].replace(":", "")) : 0; + let minutes = (rawTime[2] != null) ? (hours * 60 + rawTime[2].replace(":", "") * 1) : ((rawTime[1] != null) ? rawTime[1].replace(":", "") : 0); + let seconds = (rawTime[3] != null) ? (rawTime[3]) : 0; + let milliseconds = (rawTime[4] != null) ? (rawTime[4].replace(".", "")) : 0 + return parseFloat(`${minutes * 60 + seconds * 1}.${milliseconds * 1}`); + }, + parseTTML() { + this.lyrics = []; + let preLrc = []; + let xml = this.stringToXml(this.lyricsMediaItem); + let lyricsLines = xml.getElementsByTagName('p'); + let synced = true; + let endTimes = []; + if (xml.getElementsByTagName('tt')[0].getAttribute("itunes:timing") === "None") { + synced = false; + } + endTimes.push(0); + if (synced) { + for (let element of lyricsLines) { + let start = this.toMS(element.getAttribute('begin')) + let end = this.toMS(element.getAttribute('end')) + if (start - endTimes[endTimes.length - 1] > 5 && endTimes[endTimes.length - 1] != 0) { + preLrc.push({ + startTime: endTimes[endTimes.length - 1], + endTime: start, + line: "lrcInstrumental" + }); + } + preLrc.push({ startTime: start, endTime: end, line: element.textContent }); + endTimes.push(end); + } + // first line dot + if (preLrc.length > 0) + preLrc.unshift({ startTime: 0, endTime: preLrc[0].startTime, line: "lrcInstrumental" }); + } else { + for (let element of lyricsLines) { + preLrc.push({ startTime: 9999999, endTime: 9999999, line: element.textContent }); + } + } + this.lyrics = preLrc; + + }, + parseLyrics() { + let xml = this.stringToXml(this.lyricsMediaItem) + let json = xmlToJson(xml); + this.lyrics = json + }, + stringToXml(st) { + // string to xml + let xml = (new DOMParser()).parseFromString(st, "text/xml"); + return xml; + + }, + getCurrentTime() { + return parseFloat(this.hmsToSecondsOnly(this.parseTime(this.mk.nowPlayingItem.attributes.durationInMillis - app.mk.currentPlaybackTimeRemaining * 1000))); + }, + seekTo(time) { + this.mk.seekToTime(time); + }, + parseTime(value) { + let minutes = Math.floor(value / 60000); + let seconds = ((value % 60000) / 1000).toFixed(0); + return minutes + ":" + (seconds < 10 ? '0' : '') + seconds; + }, + parseTimeDecimal(value) { + let minutes = Math.floor(value / 60000); + let seconds = ((value % 60000) / 1000).toFixed(0); + return minutes + "." + (seconds < 10 ? '0' : '') + seconds; + }, + hmsToSecondsOnly(str) { + let p = str.split(':'), + s = 0, + m = 1; + + while (p.length > 0) { + s += m * parseInt(p.pop(), 10); + m *= 60; + } + + return s; + }, + getLyricBGStyle(start, end) { + let currentTime = this.getCurrentTime(); + // let duration = this.mk.nowPlayingItem.attributes.durationInMillis + let start2 = this.hmsToSecondsOnly(start) + let end2 = this.hmsToSecondsOnly(end) + // let 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 {} + } + }, + playMediaItemById(id, kind, isLibrary, raurl = "") { + let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; + console.log(id, truekind, isLibrary) + try { + if (truekind.includes("artist")) { + app.mk.setStationQueue({ artist: 'a-' + id }).then(() => { + app.mk.play() + }) + } else if (truekind == "radioStations") { + this.mk.setStationQueue({ url: raurl }).then(function (queue) { + MusicKit.getInstance().play() + }); + } else { + this.mk.setQueue({ + [truekind]: [id], + parameters: { l: this.mklang } + }).then(function (queue) { + MusicKit.getInstance().play() + }) + } + } catch (err) { + console.log(err) + this.playMediaItemById(id, kind, isLibrary, raurl) + } + }, + queueParentandplayChild(parent, childIndex, item) { + + /* Randomize array in-place using Durstenfeld shuffle algorithm */ + function shuffleArray(array) { + for (var i = array.length - 1; i > 0; i--) { + var j = Math.floor(Math.random() * (i + 1)); + var temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + } + + let kind = parent.substring(0, parent.indexOf(":")) + let id = parent.substring(parent.indexOf(":") + 1, parent.length) + let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; + console.log(truekind, id) + + try { + if (app.library.songs.displayListing.length > childIndex && parent == "librarysongs") { + console.log(item) + if (item && ((app.library.songs.displayListing[childIndex].id != item.id))) { + childIndex = app.library.songs.displayListing.indexOf(item) + } + + let query = app.library.songs.displayListing.map(item => new MusicKit.MediaItem(item)); + + + app.mk.stop().then(() => { + if (item) { + app.mk.setQueue({ + [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, + parameters: { l: app.mklang } + }).then(function () { + app.mk.play().then(() => { + if (app.mk.shuffleMode == 1) { + shuffleArray(query) + } else { + for (let i = 0; i < query.length; i++) { + if (query[i].id == item.id) { + query.splice(0, i + 1); + break; + } + } + } + app.mk.queue.append(query) + }) + }) + } else { + app.mk.queue.splice(0, app.mk.queue._itemIDs.length) + if (app.mk.shuffleMode == 1) { + shuffleArray(query) + } + app.mk.queue.append(query) + if (childIndex != -1) { + app.mk.changeToMediaAtIndex(childIndex) + } else { + app.mk.play() + } + + } + }) + } else if (parent.startsWith('listitem-hr')) { + app.mk.stop().then(() => { + if (app.mk.shuffleMode == 1) { + app.mk.setQueue({ + [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id + }).then(function () { + app.mk.play().then(() => { + let data = JSON.parse(parent.split('listitem-hr')[1] ?? '[]') + let itemsToPlay = {} + let u = data.map(x => x.id) + try { + data.splice(u.indexOf(item.attributes.playParams.id ?? item.id), 1) + } catch (e) { + } + if (app.mk.shuffleMode == 1) { + shuffleArray(data) + } + data.forEach(item => { + if (!itemsToPlay[item.kind]) { + itemsToPlay[item.kind] = [] + } + itemsToPlay[item.kind].push(item.id) + }) + // loop through itemsToPlay + for (let kind in itemsToPlay) { + let ids = itemsToPlay[kind] + if (ids.length > 0) { + app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] }) + } + } + }) + }) + } else { + let data = JSON.parse(parent.split('listitem-hr')[1] ?? '[]') + let itemsToPlay = {} + data.forEach(item => { + if (!itemsToPlay[item.kind]) { + itemsToPlay[item.kind] = [] + } + itemsToPlay[item.kind].push(item.id) + }) + // loop through itemsToPlay + app.mk.queue.splice(0, app.mk.queue._itemIDs.length) + let ind = 0; + for (let kind in itemsToPlay) { + let ids = itemsToPlay[kind] + if (ids.length > 0) { + if (app.mk.queue._itemIDs.length > 0) { + app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] }).then(function () { + ind += 1; + console.log(ind, Object.keys(itemsToPlay).length) + if (ind >= Object.keys(itemsToPlay).length) { + app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.attributes.playParams.id ?? item.id)) + } + } + ) + } else { + app.mk.setQueue({ [kind + "s"]: itemsToPlay[kind] }).then(function () { + ind += 1; + console.log(ind, Object.keys(itemsToPlay).length) + if (ind >= Object.keys(itemsToPlay).length) { + app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.attributes.playParams.id ?? item.id)) + } + } + ) + } + } + + } + } + }) + } else { + app.mk.stop().then(() => { + if (truekind == "playlists" && (id.startsWith("p.") || id.startsWith("pl.u"))) { + app.mk.setQueue({ + [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, + parameters: { l: app.mklang } + }).then(function () { + app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1).then(function () { + if ((app.showingPlaylist && app.showingPlaylist.id == id)) { + let query = app.showingPlaylist.relationships.tracks.data.map(item => new MusicKit.MediaItem(item)); + let u = query; + if (app.mk.shuffleMode == 1) { + shuffleArray(u) + } else { + for (let i = 0; i < app.showingPlaylist.relationships.tracks.data.length; i++) { + if (app.showingPlaylist.relationships.tracks.data[i].id == item.id) { + u.splice(0, i + 1); + break; + } + } + } + app.mk.queue.append(u) + } else { + app.getPlaylistFromID(id, true).then(function () { + let query = app.showingPlaylist.relationships.tracks.data.map(item => new MusicKit.MediaItem(item)); + let u = query; + if (app.mk.shuffleMode == 1) { + shuffleArray(u) + } else { + for (let i = 0; i < app.showingPlaylist.relationships.tracks.data.length; i++) { + if (app.showingPlaylist.relationships.tracks.data[i].id == item.id) { + u.splice(0, i + 1); + break; + } + } + } + app.mk.queue.append(u) + }) + } + }) + + }) + } else { + this.mk.setQueue({ + [truekind]: [id], + parameters: { l: this.mklang } + }).then(function (queue) { + if (item && ((queue._itemIDs[childIndex] != item.id))) { + childIndex = queue._itemIDs.indexOf(item.id) + } + if (childIndex != -1) { + app.mk.changeToMediaAtIndex(childIndex) + } else if (item) { + app.mk.playNext({ + [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id + }).then(function () { + app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1) + app.mk.play() + }) + } else { + app.mk.play() + } + }) + } + }) + } + } catch (err) { + console.log(err) + try { + app.mk.stop() + } catch (e) { + } + this.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url) + } + + }, + friendlyTypes(type) { + // use switch statement to return friendly name for media types "songs,artists,albums,playlists,music-videos,stations,apple-curators,curators" + switch (type) { + case "song": + return app.getLz('term.songs') + break; + case "artist": + return app.getLz('term.artists') + break; + case "album": + return app.getLz('term.albums') + break; + case "playlist": + return app.getLz('term.playlists') + break; + case "music_video": + return app.getLz('term.musicVideos') + break; + case "station": + return app.getLz('term.stations') + break; + case "apple-curator": + return app.getLz('term.appleCurators') + break; + case "radio_show": + return app.getLz('term.radioShows') + break; + case "record_label": + return app.getLz('term.recordLabels') + break; + case "radio_episode": + return app.getLz('podcast.episodes') + break; + case "video_extra": + return app.getLz('term.videoExtras') + break; + case "curator": + return app.getLz('term.curators') + break; + case "top": + return app.getLz('term.top') + break; + default: + return type + break; + } + }, + async searchQuery(term = this.search.term) { + let self = this + if (term == "") { + return + } + //this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${this.search.term}` + this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${encodeURIComponent(this.search.term)}`, { + types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels", + "relate[editorial-items]": "contents", + "include[editorial-items]": "contents", + "include[albums]": "artists", + "include[artists]": "artists", + "include[songs]": "artists,albums", + "include[music-videos]": "artists", + "extend": "artistUrl", + "fields[artists]": "url,name,artwork,hero", + "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url", + "with": "serverBubbles,lyricHighlights", + "art[url]": "c,f", + "omit[resource]": "autos", + "platform": "web", + limit: 25, + l: this.mklang + }).then(function (results) { + results.data.results["meta"] = results.data.meta + self.search.results = results.data.results + }) + + await app.mk.api.v3.music(`v1/social/${app.mk.storefrontId}/search?term=${app.search.term}`, { + types: ["playlists", "social-profiles"], + limit: 25, + with: ["serverBubbles", "lyricSnippet"], + "art[url]": "f", + "art[social-profiles:url]": "c" + }, { includeResponseMeta: !0 }).then(function (results) { + results.data.results["meta"] = results.data.meta + self.search.resultsSocial = results.data.results + }) + }, + async inLibrary(items = []) { + let types = [] + + for (let item of items) { + let type = item.type + if (type.slice(-1) != "s") { + type += "s" + } + type = type.replace("library-", "") + let id = item.attributes.playParams.catalogId ?? item.attributes.playParams.id ?? item.id + + let index = types.findIndex(function (type) { + return type.type == this + }, type) + if (index == -1) { + types.push({ type: type, id: [id] }) + } else { + types[index].id.push(id) + } + } + let 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", + relate: "library", + fields: "inLibrary" + }, + ...types2 + })).data.data + }, + isInLibrary(playParams) { + let self = this + let id = "" + // ugly code to check if current playback item is in library + if (typeof playParams == "undefined") { + return true + } + if (playParams["isLibrary"]) { + return true + } else if (playParams["catalogId"]) { + id = playParams["catalogId"] + } else if (playParams["id"]) { + id = playParams["id"] + } + let found = this.library.songs.listing.filter((item) => { + if (item["attributes"]) { + if (item["attributes"]["playParams"] && (item["attributes"]["playParams"]["catalogId"] == id)) { + return item; + } + } + }) + if (found.length != 0) { + return true + } else { + return false + } + }, + mkReady() { + if (this.mk["nowPlayingItem"]) { + return true + } else { + return false + } + }, + getMediaItemArtwork(url, height = 64, width) { + if (typeof url == "undefined" || url == "") { + return "https://beta.music.apple.com/assets/product/MissingArtworkMusic.svg" + } + let newurl = `${url.replace('{w}', width ?? height).replace('{h}', height).replace('{f}', "webp").replace('{c}', ((width === 900) ? "sr" : "cc"))}`; + + if (newurl.includes("900x516")) { + newurl = newurl.replace("900x516cc", "900x516sr").replace("900x516bb", "900x516sr"); + } + return newurl + }, + _rgbToRgb(rgb = [0, 0, 0]) { + // if rgb + return `rgb(${rgb[0]},${rgb[1]},${rgb[2]})` + }, + getNowPlayingArtworkBG(size = 32, force = false) { + let self = this + if (typeof this.mk.nowPlayingItem === "undefined") return; + let bginterval = setInterval(() => { + if (!this.mkReady()) { + return "" + } + + try { + if ((this.mk.nowPlayingItem && this.mk.nowPlayingItem["id"] != this.currentTrackID && document.querySelector('.bg-artwork')) || force) { + if (document.querySelector('.bg-artwork')) { + clearInterval(bginterval); + } + this.currentTrackID = this.mk.nowPlayingItem["id"]; + document.querySelector('.bg-artwork').src = ""; + if (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"]) { + getBase64FromUrl(this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"].replace('{w}', size).replace('{h}', size)).then(img => { + document.querySelectorAll('.bg-artwork').forEach(artwork => { + artwork.src = img; + }) + self.$store.commit("setLCDArtwork", img) + }) + try { + clearInterval(bginterval); + } catch (err) { + } + } else { + this.setLibraryArtBG() + } + } else if (this.mk.nowPlayingItem["id"] == this.currentTrackID) { + try { + clearInterval(bginterval); + } catch (err) { + } + } + } catch (e) { + if (this.mk.nowPlayingItem && this.mk.nowPlayingItem["id"] && document.querySelector('.bg-artwork')) { + this.setLibraryArtBG() + try { + clearInterval(bginterval); + } catch (err) { + } + } + } + }, 200) + }, + async getCurrentArtURL() { + try { + let artworkSize = 50 + if (app.getThemeDirective("lcdArtworkSize") != "") { + artworkSize = app.getThemeDirective("lcdArtworkSize") + } else if (this.cfg.visual.directives.windowLayout == "twopanel") { + artworkSize = 70 + } + this.currentArtUrl = ''; + this.currentArtUrlRaw = ''; + if (app.mk.nowPlayingItem != null && app.mk.nowPlayingItem.attributes != null && app.mk.nowPlayingItem.attributes.artwork != null && app.mk.nowPlayingItem.attributes.artwork.url != null && app.mk.nowPlayingItem.attributes.artwork.url != '') { + this.currentArtUrlRaw = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '') + this.currentArtUrl = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '').replace('{w}', artworkSize).replace('{h}', artworkSize); + try { + document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); + } catch (e) { + } + } else { + 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) { + this.currentArtUrlRaw = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? '') + this.currentArtUrl = (data["attributes"]["artwork"]["url"] ?? '').replace('{w}', artworkSize).replace('{h}', artworkSize); + ipcRenderer.send('updateRPCImage', this.currentArtUrl ?? ''); + try { + document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); + } catch (e) { + } + } else { + this.currentArtUrlRaw = '' + this.currentArtUrl = ''; + try { + document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); + } catch (e) { + } + } + } + } catch (e) { + + } + }, + async setLibraryArt() { + if (typeof this.mk.nowPlayingItem === "undefined") return; + try { + 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 !== "") { + document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', 'url("' + (data["attributes"]["artwork"]["url"]).toString() + '")'); + } else { + document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("")`); + } + } catch (e) { + } + }, + async setLibraryArtBG() { + if (typeof this.mk.nowPlayingItem === "undefined") return; + try { + 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 !== "") { + getBase64FromUrl((data["attributes"]["artwork"]["url"]).toString()).then(img => { + document.querySelector('.bg-artwork').forEach(artwork => { + artwork.src = img; + }) + self.$store.commit("setLCDArtwork", img) + }) + } + } catch (e) { + } + + }, + quickPlay(query) { + let self = this + MusicKit.getInstance().api.search(query, { limit: 2, types: 'songs' }).then(function (data) { + MusicKit.getInstance().setQueue({ + song: data["songs"]['data'][0]["id"], + parameters: { l: app.mklang } + }).then(function (queue) { + MusicKit.getInstance().play() + setTimeout(() => { + self.$forceUpdate() + }, 1000) + }) + }) + }, + async getRating(item) { + let type = item.type.slice(-1) === "s" ? item.type : item.type + "s" + let id = item.attributes?.playParams?.catalogId ? item.attributes.playParams.catalogId : (item.attributes?.playParams?.id ?? item.id) + if (item.id != null && (item.id.toString()).startsWith("i.")) { + if (!type.startsWith("library-")) { + type = "library-" + type + } + id = item.id + } + let response = await this.mk.api.v3.music(`/v1/me/ratings/${type}?platform=web&ids=${type.includes('library') ? item.id : id}`) + if (response.data.data.length != 0) { + let value = response.data.data[0].attributes.value + return value + } else { + return 0 + } + }, + love(item) { + let type = item.type.slice(-1) === "s" ? item.type : item.type + "s" + let id = item.attributes?.playParams?.catalogId ? item.attributes.playParams.catalogId : (item.attributes?.playParams?.id ?? item.id) + if (item.id != null && (item.id.toString()).startsWith("i.")) { + if (!type.startsWith("library-")) { + type = "library-" + type + } + id = item.id + } + this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, { + fetchOptions: { + method: "PUT", + body: JSON.stringify({ + "type": "rating", + "attributes": { + "value": 1 + } + }) + } + }) + }, + dislike(item) { + let type = item.type.slice(-1) === "s" ? item.type : item.type + "s" + let id = item.attributes?.playParams?.catalogId ? item.attributes.playParams.catalogId : (item.attributes?.playParams?.id ?? item.id) + if (item.id != null && (item.id.toString()).startsWith("i.")) { + if (!type.startsWith("library-")) { + type = "library-" + type + } + id = item.id + } + this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, { + fetchOptions: { + method: "PUT", + body: JSON.stringify({ + "type": "rating", + "attributes": { + "value": -1 + } + }) + } + }) + }, + unlove(item) { + let type = item.type.slice(-1) === "s" ? item.type : item.type + "s" + let id = item.attributes.playParams.catalogId ? item.attributes.playParams.catalogId : item.id + if (item.id.startsWith("i.")) { + if (!type.startsWith("library-")) { + type = "library-" + type + } + id = item.id + } + this.mk.api.v3.music(`/v1/me/ratings/${type}/${id}`, {}, { + fetchOptions: { + method: "DELETE", + } + }) + }, + checkScrollDirectionIsUp(event) { + if (event.wheelDelta) { + return event.wheelDelta > 0; + } + return event.deltaY < 0; + }, + volumeUp() { + if ((app.mk.volume + app.cfg.audio.volumeStep) > 1) { + app.mk.volume = app.cfg.audio.maxVolume; + console.log('setting max volume') + } else { + console.log('volume up') + app.mk.volume = ((app.mk.volume * 100) + (app.cfg.audio.volumeStep * 100)) / 100 + } + }, + volumeDown() { + if ((app.mk.volume - app.cfg.audio.volumeStep) < 0) { + app.mk.volume = 0; + console.log('setting volume to 0') + } else { + console.log('volume down') + app.mk.volume = ((app.mk.volume * 100) - (app.cfg.audio.volumeStep * 100)) / 100 + } + }, + volumeWheel(event) { + app.checkScrollDirectionIsUp(event) ? this.volumeUp() : this.volumeDown() + }, + muteButtonPressed() { + if (this.cfg.audio.muted) { + this.mk.volume = this.cfg.audio.lastVolume; + this.cfg.audio.muted = false; + } else { + this.cfg.audio.lastVolume = this.cfg.audio.volume; + this.mk.volume = 0; + this.cfg.audio.muted = true; + } + }, + checkMuteChange() { + if (this.cfg.audio.muted) { + this.cfg.audio.muted = false; + } + }, + async apiCall(url, callback) { + const xmlHttp = new XMLHttpRequest(); + + xmlHttp.onreadystatechange = (e) => { + if (xmlHttp.readyState !== 4) { + return; + } + + if (xmlHttp.status === 200) { + // console.log('SUCCESS', xmlHttp.responseText); + callback(JSON.parse(xmlHttp.responseText)); + } else { + console.warn('request_error'); + } + }; + + xmlHttp.open("GET", url); + xmlHttp.setRequestHeader("Authorization", "Bearer " + MusicKit.getInstance().developerToken); + xmlHttp.setRequestHeader("Music-User-Token", "" + MusicKit.getInstance().musicUserToken); + xmlHttp.setRequestHeader("Accept", "application/json"); + xmlHttp.setRequestHeader("Content-Type", "application/json"); + xmlHttp.responseType = "text"; + xmlHttp.send(); + }, + fetchPlaylist(id, callback) { + // id can be found in playlist.attributes.playParams.globalId + // this.mk.api. + 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 + }, + windowFocus(val) { + if (val) { + document.querySelectorAll(".animated-artwork-video").forEach(el => { + el.play() + }) + document.querySelector("body").classList.remove("stopanimation") + this.animateBackground = true + } else { + document.querySelectorAll(".animated-artwork-video").forEach(el => { + el.pause() + }) + document.querySelector("body").classList.add("stopanimation") + this.animateBackground = false + } + }, + async nowPlayingContextMenu(event) { + let self = this + let data_type = this.mk.nowPlayingItem.playParams.kind + let item_id = this.mk.nowPlayingItem.attributes.playParams.id ?? this.mk.nowPlayingItem.id + let isLibrary = this.mk.nowPlayingItem.attributes.playParams.isLibrary ?? false + let params = { "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "t": "1" } + app.selectedMediaItems = [] + app.select_selectMediaItem(item_id, data_type, 0, '12344', isLibrary) + let useMenu = "normal" + let menus = { + multiple: { + items: [] + }, + normal: { + headerItems: [{ + "icon": "./assets/feather/heart.svg", + "id": "love", + "name": app.getLz('action.love'), + "hidden": false, + "disabled": true, + "action": function () { + app.love(app.mk.nowPlayingItem) + } + }, + { + "icon": "./assets/feather/heart.svg", + "id": "unlove", + "active": true, + "name": app.getLz('action.unlove'), + "hidden": true, + "action": function () { + app.unlove(app.mk.nowPlayingItem) + } + }, + { + "icon": "./assets/feather/thumbs-down.svg", + "id": "dislike", + "name": app.getLz('action.dislike'), + "hidden": false, + "disabled": true, + "action": function () { + app.dislike(app.mk.nowPlayingItem) + } + }, + { + "icon": "./assets/feather/thumbs-down.svg", + "id": "undo_dislike", + "name": app.getLz('action.undoDislike'), + "active": true, + "hidden": true, + "action": function () { + app.unlove(app.mk.nowPlayingItem) + } + }, + ], + items: [ + { + "icon": "./assets/feather/plus.svg", + "id": "addToLibrary", + "name": app.getLz('action.addToLibrary') + " ...", + "disabled": true, + "action": function () { + app.addToLibrary(app.mk.nowPlayingItem.id); + } + }, + { + "id": "removeFromLibrary", + "icon": "./assets/feather/x-circle.svg", + "name": app.getLz('action.removeFromLibrary'), + "hidden": true, + "action": function () { + self.removeFromLibrary() + } + }, + { + "icon": "./assets/feather/list.svg", + "name": app.getLz('action.addToPlaylist') + " ...", + "action": function () { + app.promptAddToPlaylist() + } + }, + { + "icon": "./assets/feather/radio.svg", + "name": app.getLz('action.startRadio'), + "action": function () { + app.mk.setStationQueue({ song: app.mk.nowPlayingItem.id }).then(() => { + app.mk.play() + app.selectedMediaItems = [] + }) + } + }, + { + "icon": "./assets/feather/user.svg", + "name": app.getLz('action.goToArtist'), + "action": function () { + app.appRoute(`artist/${app.mk.nowPlayingItem.relationships.artists.data[0].id}`) + } + }, + { + "icon": "./assets/feather/disc.svg", + "name": app.getLz('action.goToAlbum'), + "action": function () { + app.appRoute(`album/${app.mk.nowPlayingItem.relationships.albums.data[0].id}`) + } + }, + { + "icon": "./assets/feather/share.svg", + "name": app.getLz('action.share'), + "action": function () { + app.mkapi(app.mk.nowPlayingItem.attributes?.playParams?.kind ?? app.mk.nowPlayingItem.type ?? 'songs', false, app.mk.nowPlayingItem._songId ?? (app.mk.nowPlayingItem.songId ?? app.mk.nowPlayingItem.id) ?? '').then(u => { + app.copyToClipboard((u.data.data.length && u.data.data.length > 0) ? u.data.data[0].attributes.url : u.data.data.attributes.url) + }) + } + }, + { + "icon": "./assets/feather/share.svg", + "name": `${app.getLz('action.share')} (song.link)`, + "action": function () { + app.mkapi(app.mk.nowPlayingItem.attributes?.playParams?.kind ?? app.mk.nowPlayingItem.type ?? 'songs', false, app.mk.nowPlayingItem._songId ?? (app.mk.nowPlayingItem.songId ?? app.mk.nowPlayingItem.id) ?? '').then(u => { + app.songLinkShare((u.data.data.length && u.data.data.length > 0) ? u.data.data[0].attributes.url : u.data.data.attributes.url) + }) + } + } + ] + } + } + if (this.contextExt) { + if (this.contextExt.normal) { + menus.normal.items = menus.normal.items.concat(this.contextExt.normal) + } + } + this.showMenuPanel(menus[useMenu], event) + + try { + let result = await this.inLibrary([this.mk.nowPlayingItem]) + if (result[0].attributes.inLibrary) { + menus.normal.items.find(x => x.id == 'addToLibrary').hidden = true + menus.normal.items.find(x => x.id == 'removeFromLibrary').hidden = false + } else { + menus.normal.items.find(x => x.id == 'addToLibrary').disabled = false + } + } catch (e) { + e = null + } + + try { + let rating = await app.getRating(app.mk.nowPlayingItem) + if (rating == 0) { + menus.normal.headerItems.find(x => x.id == 'love').disabled = false + menus.normal.headerItems.find(x => x.id == 'dislike').disabled = false + } else if (rating == 1) { + menus.normal.headerItems.find(x => x.id == 'unlove').hidden = false + menus.normal.headerItems.find(x => x.id == 'love').hidden = true + } else if (rating == -1) { + menus.normal.headerItems.find(x => x.id == 'undo_dislike').hidden = false + menus.normal.headerItems.find(x => x.id == 'dislike').hidden = true + } + } catch (err) { + + } + }, + LastFMDeauthorize() { + ipcRenderer.invoke('setStoreValue', 'lastfm.enabled', false).catch((e) => console.error(e)); + ipcRenderer.invoke('setStoreValue', 'lastfm.auth_token', '').catch((e) => console.error(e)); + app.cfg.lastfm.auth_token = ""; + app.cfg.lastfm.enabled = false; + const element = document.getElementById('lfmConnect'); + element.innerHTML = app.getLz('term.connect'); + element.onclick = app.LastFMAuthenticate; + }, + LastFMAuthenticate() { + console.log("[LastFM] Received LastFM authentication callback") + const element = document.getElementById('lfmConnect'); + // new key : f9986d12aab5a0fe66193c559435ede3 + window.open('https://www.last.fm/api/auth?api_key=f9986d12aab5a0fe66193c559435ede3&cb=cider://auth/lastfm'); + element.innerText = app.getLz('term.connecting') + '...'; + + /* Just a timeout for the button */ + setTimeout(() => { + if (element.innerText === app.getLz('term.connecting') + '...') { + element.innerText = app.getLz('term.connect'); + console.warn('[LastFM] Attempted connection timed out.'); + } + }, 20000); + + ipcRenderer.on('LastfmAuthenticated', function (_event, lfmAuthKey) { + app.cfg.lastfm.auth_token = lfmAuthKey; + app.cfg.lastfm.enabled = true; + element.innerHTML = `${app.getLz('term.disconnect')}\n

(${app.getLz('term.authed')}: ${lfmAuthKey})

`; + element.onclick = app.LastFMDeauthorize; + }); + }, + /** + parseSCTagToRG: function (tag) { + let soundcheck = tag.split(" ") + let numbers = [] + for (item of soundcheck) { + numbers.push(parseInt(item, 16)) + + } + numbers.shift() + //let gain = Math.log10((Math.max(numbers[0], numbers[1]) ?? 1000) / 1000.0) * -10 + let peak = Math.max(numbers[6], numbers[7]) / 32768.0 + let gain = Math.pow(10, ((-7.63 - (Math.log10(peak) * 20)) / 20))// EBU R 128 Compliant + return { + gain: gain, + peak: peak + } + },*/ + fullscreen(flag) { + if (flag) { + ipcRenderer.send('setFullScreen', true); + if (app.mk.nowPlayingItem.type && app.mk.nowPlayingItem.type.toLowerCase().includes("video")) { + document.querySelector('video#apple-music-video-player').requestFullscreen() + } else { + app.appMode = 'fullscreen'; + } + document.addEventListener('keydown', event => { + if (event.key === 'Escape' && app.appMode === 'fullscreen') { + this.fullscreen(false); + } + }); + } else { + ipcRenderer.send('setFullScreen', false); + app.appMode = 'player'; + } + }, + miniPlayer(flag) { + if (flag) { + this.tmpWidth = window.innerWidth; + this.tmpHeight = window.innerHeight; + ipcRenderer.send('unmaximize'); + ipcRenderer.send('windowmin', 250, 250) + ipcRenderer.send('windowresize', 300, 300, false) + app.appMode = 'mini'; + } else { + ipcRenderer.send('windowmin', 844, 410) + ipcRenderer.send('windowresize', this.tmpWidth, this.tmpHeight, false) + ipcRenderer.send('windowontop', false) + this.cfg.visual.miniplayer_top_toggle = true; + app.appMode = 'player'; + } + }, + pinMiniPlayer() { + if (this.cfg.visual.miniplayer_top_toggle) { + ipcRenderer.send('windowontop', true) + this.cfg.visual.miniplayer_top_toggle = false + } else { + ipcRenderer.send('windowontop', false) + this.cfg.visual.miniplayer_top_toggle = true; + } + }, + 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)}` + }, + toggleHideUserInfo() { + if (this.chrome.hideUserInfo) { + this.cfg.visual.showuserinfo = true + this.chrome.hideUserInfo = false + } else { + this.cfg.visual.showuserinfo = false + this.chrome.hideUserInfo = true + } + }, + isElementOverflowing(selector) { + try { + let element = document.querySelector(selector); + var overflowX = element.offsetWidth < element.scrollWidth, + overflowY = element.offsetHeight < element.scrollHeight; + element.setAttribute('data-value', '\xa0\xa0\xa0\xa0' + element.textContent); + + return (overflowX || overflowY); + } catch (e) { + return false + } + }, + async showWebRemoteQR() { + //this.webremoteqr = await ipcRenderer.invoke('setRemoteQR','') + this.webremoteurl = await ipcRenderer.invoke('showQR', '') + //this.modals.qrcode = true; + }, + checkMarquee() { + if (isElementOverflowing('#app-main > div.app-chrome > div.app-chrome--center > div > div > div.playback-info > div.song-artist') == true) { + document.getElementsByClassName('song-artist')[0].classList.add('marquee'); + document.getElementsByClassName('song-artist')[1].classList.add('marquee-after'); + } + if (isElementOverflowing('#app-main > div.app-chrome > div.app-chrome--center > div > div > div.playback-info > div.song-name') == true) { + document.getElementsByClassName('song-name')[0].classList.add('marquee'); + document.getElementsByClassName('song-name')[1].classList.add('marquee-after'); + } + }, + closeWindow() { + ipcRenderer.send('close'); + }, + darwinShare(url) { + ipcRenderer.send('share-menu', url) + }, + arrayToChunk(arr, chunkSize) { + let R = []; + for (let i = 0, len = arr.length; i < len; i += chunkSize) { + R.push(arr.slice(i, i + chunkSize)); + } + return R; + }, + SpacePause() { + const elems = document.querySelectorAll('input'); + for (let elem of elems) { + if (elem === document.activeElement) { + return; + } + } + if (!this.isDev) // disable in dev mode to keep my sanity + MusicKitInterop.playPause(); + }, + async MKJSLang() { + let u = this.cfg.general.language; + // use MusicKit.getInstance or crash + try { + let item = await MusicKit.getInstance().api.v3.music(`v1/storefronts/${app.mk.storefrontId}`) + let langcodes = item.data.data[0].attributes.supportedLanguageTags; + if (langcodes) langcodes = langcodes.map(function (u) { + return u.replace(/-Han[s|t]/i, "").toLowerCase() + }) + console.log(langcodes) + let sellang = "" + if (u && langcodes.includes(u.toLowerCase().replace('_', "-"))) { + sellang = ((u.toLowerCase()).replace('_', "-")) + } else if (u && u.includes('_') && langcodes.includes(((u.toLowerCase()).replace('_', "-")).split("-")[0])) { + sellang = ((u.toLowerCase()).replace('_', "-")).split("-")[0] + } + if (sellang == "") sellang = (item.data.data[0].attributes.defaultLanguageTag).toLowerCase() + + // Fix weird locales: + if (sellang == "iw") sellang = "iw-il" + sellang = sellang.replace(/-Han[s|t]/i, "").toLowerCase() + + console.log(sellang) + return await sellang + } catch (err) { + console.log('locale err', err) + let langcodes = ['af', 'sq', 'ar', 'eu', 'bg', 'be', 'ca', 'zh', 'zh-tw', 'zh-cn', 'zh-hk', 'zh-sg', 'hr', 'cs', 'da', 'nl', 'nl-be', 'en', 'en-us', 'en-eg', 'en-au', 'en-gb', 'en-ca', 'en-nz', 'en-ie', 'en-za', 'en-jm', 'en-bz', 'en-tt', 'en-001', 'et', 'fo', 'fa', 'fi', 'fr', 'fr-ca', 'gd', 'de', 'de-ch', 'el', 'he', 'hi', 'hu', 'is', 'id', 'it', 'ja', 'ko', 'lv', 'lt', 'mk', 'mt', 'no', 'nb', 'nn', 'pl', 'pt-br', 'pt', 'rm', 'ro', 'ru', 'sr', 'sk', 'sl', 'es', 'es-mx', 'es-419', 'sv', 'th', 'ts', 'tn', 'tr', 'uk', 'ur', 've', 'vi', 'xh', 'yi', 'zu', 'ms', 'iw', 'lo', 'tl', 'kk', 'ta', 'te', 'bn', 'ga', 'ht', 'la', 'pa', 'sa']; + let sellang = "en" + if (u && langcodes.includes(u.toLowerCase().replace('_', "-"))) { + sellang = ((u.toLowerCase()).replace('_', "-")) + } else if (u && u.includes('_') && langcodes.includes(((u.toLowerCase()).replace('_', "-")).split("-")[0])) { + sellang = ((u.toLowerCase()).replace('_', "-")).split("-")[0] + } + if (sellang.startsWith("en") && this.mk.storefrontId != "us") sellang = "en-gb" + return await sellang + } + }, + skipToNextItem() { + app.prevButtonBackIndicator = false; + // app.mk.skipToNextItem() is buggy somehow so use this + if (this.mk.queue.nextPlayableItemIndex != -1 && this.mk.queue.nextPlayableItemIndex != null) + this.mk.changeToMediaAtIndex(this.mk.queue.nextPlayableItemIndex); + }, + skipToPreviousItem() { + // app.mk.skipToPreviousItem() is buggy somehow so use this + if (this.mk.queue.previousPlayableItemIndex != -1 && this.mk.queue.previousPlayableItemIndex != null) + this.mk.changeToMediaAtIndex(this.mk.queue.previousPlayableItemIndex); + }, + mediaKeyFixes() { + navigator.mediaSession.setActionHandler('previoustrack', function () { + app.prevButton() + }); + navigator.mediaSession.setActionHandler('nexttrack', function () { + app.skipToNextItem() + }); + }, + checkForUpdate() { + ipcRenderer.send('check-for-update') + ipcRenderer.on('update-response', (event, res) => { + if (res === "update-not-available") { + notyf.error(app.getLz(`settings.notyf.updateCider.${res}`)) + } else if (res === "update-downloaded") { + notyf.success(app.getLz(`settings.notyf.updateCider.${res}`)) + } else if (res === "update-error") { + notyf.error(app.getLz(`settings.notyf.updateCider.${res}`)) + } else if (res === "update-timeout") { + notyf.error(app.getLz(`settings.notyf.updateCider.${res}`)) + } + + }) + }, + } +}) + + +export { app } \ No newline at end of file diff --git a/src/renderer/main/vuex-store.js b/src/renderer/main/vuex-store.js new file mode 100644 index 00000000..fb2b774f --- /dev/null +++ b/src/renderer/main/vuex-store.js @@ -0,0 +1,20 @@ +const store = new Vuex.Store({ + state: { + library: { + // songs: ipcRenderer.sendSync("get-library-songs"), + // albums: ipcRenderer.sendSync("get-library-albums"), + // recentlyAdded: ipcRenderer.sendSync("get-library-recentlyAdded"), + // playlists: ipcRenderer.sendSync("get-library-playlists") + }, + artwork: { + playerLCD: "" + } + }, + mutations: { + setLCDArtwork(state, artwork) { + state.artwork.playerLCD = artwork + } + } +}) + +export {store} \ No newline at end of file diff --git a/src/renderer/js/WSAPI_Interop.js b/src/renderer/main/wsapi_interop.js similarity index 92% rename from src/renderer/js/WSAPI_Interop.js rename to src/renderer/main/wsapi_interop.js index 16002fae..563b2e30 100644 --- a/src/renderer/js/WSAPI_Interop.js +++ b/src/renderer/main/wsapi_interop.js @@ -49,13 +49,13 @@ const wsapi = { }, musickitApi(method, id, params, library = false) { if (library) { - MusicKit.getInstance().api.library[method](id, params).then((results)=>{ - ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method) - }) + MusicKit.getInstance().api.library[method](id, params).then((results)=>{ + ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method) + }) } else { - MusicKit.getInstance().api[method](id, params).then((results)=>{ - ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method) - }) + MusicKit.getInstance().api[method](id, params).then((results)=>{ + ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method) + }) } }, getPlaybackState () { @@ -111,4 +111,6 @@ const wsapi = { getmaxVolume() { ipcRenderer.send('wsapi-returnvolumeMax',JSON.stringify(app.cfg.audio.maxVolume)); } -} \ No newline at end of file +} + +export {wsapi} \ No newline at end of file diff --git a/src/renderer/views/main.ejs b/src/renderer/views/main.ejs index 5d18d61a..0f230cb4 100644 --- a/src/renderer/views/main.ejs +++ b/src/renderer/views/main.ejs @@ -17,24 +17,24 @@ Cider - - - - - - - - + + + + + + + + - - - - - - - + + + + + + + -
- <%- include("../assets/cider-round.svg") %> -
-
- -
- <%- include('app/chrome-top'); %> - <%- include('app/app-navigation'); %> - <%- include('app/chrome-bottom'); %> -
-
- -
- -
-
- -
- -
-
- <%- include('app/panels'); %> -
-
+
+ <%- include("../assets/cider-round.svg") %> +
+
+ +
+ <%- include('app/chrome-top'); %> + <%- include('app/app-navigation'); %> + <%- include('app/chrome-bottom'); %> +
+
+ +
+ +
+
+ +
+ + +
+
+ <%- include('app/panels'); %> +
+
-<% for(var i=0; i < Object.keys(env.components).length ; i++) {%> - <%- include(env.components[i]); %> -<% } %> + <% for(var i=0; i < Object.keys(env.components).length ; i++) {%> + <%- include(env.components[i]); %> + <% } %> - - - + + - - - \ No newline at end of file diff --git a/src/renderer/views/components/mediaitem-square.ejs b/src/renderer/views/components/mediaitem-square.ejs index 3d8724cb..7fcc7dfe 100644 --- a/src/renderer/views/components/mediaitem-square.ejs +++ b/src/renderer/views/components/mediaitem-square.ejs @@ -36,7 +36,7 @@
-
{{ item.attributes.name }}
+
{{ item.attributes?.name ?? (item.relationships?.contents?.data[0]?.attributes?.name ?? '') }}
Date: Sat, 5 Mar 2022 21:20:39 +0700 Subject: [PATCH 35/37] fix miniplayer on top, add blur toggle on windows --- package.json | 3 ++- src/i18n/en_US.json | 3 +++ src/main/base/browserwindow.ts | 16 ++++++++++++++-- src/main/base/store.ts | 1 + src/renderer/main/vueapp.js | 13 ++++++++----- .../views/components/mediaitem-square.ejs | 2 +- src/renderer/views/components/miniplayer.ejs | 3 +++ src/renderer/views/pages/settings.ejs | 16 ++++++++++++++++ 8 files changed, 48 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index be0b3b75..89aeaf73 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "electron-notarize": "^1.1.1", "electron-store": "^8.0.1", "electron-updater": "^4.6.5", + "electron-vibrancy-updated": "git+https://github.com/ciderapp/electron-vibrancy-updated", "electron-window-state": "^5.0.3", "express": "^4.17.3", "get-port": "^5.1.1", @@ -75,7 +76,7 @@ "@types/express": "^4.17.13", "@types/qrcode-terminal": "^0.12.0", "@types/ws": "^8.5.1", - "electron": "git+https://github.com/castlabs/electron-releases.git", + "electron": "git+https://github.com/castlabs/electron-releases.git#16-x-y", "electron-builder": "^22.14.13", "electron-builder-notarize-pkg": "^1.2.0", "electron-webpack": "^2.8.2", diff --git a/src/i18n/en_US.json b/src/i18n/en_US.json index 7e402929..d224beb8 100644 --- a/src/i18n/en_US.json +++ b/src/i18n/en_US.json @@ -201,6 +201,7 @@ "action.unfollow": "Unfollow", "action.unfollow.success": "Unfollowed", "action.unfollow.error": "Error Unfollowing", + "action.relaunch.confirm": "Do you want to relaunch Cider?", "action.playNext": "Play Next", "action.playLater": "Play Later", "action.startRadio": "Start Radio", @@ -315,6 +316,8 @@ "settings.option.visual.hardwareAcceleration.description": "Requires relaunch", "settings.header.visual.hardwareAcceleration.default": "Default", "settings.header.visual.hardwareAcceleration.webGPU": "WebGPU", + "settings.option.visual.transparent": "Transparent frame", + "settings.option.visual.transparent.description": "Transparent frame (needs Theme Support , requires relaunch)", "settings.header.visual.theme": "Theme", "settings.option.visual.theme.github.download": "Install from GitHub URL", "settings.option.visual.theme.github.explore": "Explore GitHub Themes", diff --git a/src/main/base/browserwindow.ts b/src/main/base/browserwindow.ts index 316a0e01..82dc46dd 100644 --- a/src/main/base/browserwindow.ts +++ b/src/main/base/browserwindow.ts @@ -301,8 +301,9 @@ export class BrowserWindow { break; case "win32": - this.options.backgroundColor = "#1E1E1E"; - this.options.transparent = false; + if (!(utils.getStoreValue('visual.transparent') ?? false)){ + this.options.backgroundColor = "#1E1E1E";} else { + this.options.transparent = true;} break; case "linux": this.options.backgroundColor = "#1E1E1E"; @@ -321,6 +322,11 @@ export class BrowserWindow { this.startWebServer(); BrowserWindow.win = new bw(this.options); + if (process.platform === "win32" && (utils.getStoreValue('visual.transparent') ?? false)) { + var electronVibrancy = require('electron-vibrancy-updated'); + electronVibrancy.SetVibrancy(BrowserWindow.win, 0); + + } const ws = new wsapi(BrowserWindow.win) ws.InitWebSockets() // and load the renderer. @@ -797,9 +803,15 @@ export class BrowserWindow { ipcMain.on('setFullScreen', (_event, flag) => { BrowserWindow.win.setFullScreen(flag) }) + //Fullscreen ipcMain.on('detachDT', (_event, _) => { BrowserWindow.win.webContents.openDevTools({mode: 'detach'}); + }) + + ipcMain.on('relaunchApp',(_event, _) => { + app.relaunch() + app.exit() }) diff --git a/src/main/base/store.ts b/src/main/base/store.ts index 0a77de97..f1ea8dc2 100644 --- a/src/main/base/store.ts +++ b/src/main/base/store.ts @@ -105,6 +105,7 @@ export class Store { "bg_artwork_rotation": false, "hw_acceleration": "default", // default, webgpu, disabled "showuserinfo": true, + "transparent": false, "miniplayer_top_toggle": true, "directives": { "windowLayout": "default" diff --git a/src/renderer/main/vueapp.js b/src/renderer/main/vueapp.js index c2d2e163..f4ca97b2 100644 --- a/src/renderer/main/vueapp.js +++ b/src/renderer/main/vueapp.js @@ -3863,17 +3863,20 @@ const app = new Vue({ ipcRenderer.send('windowmin', 844, 410) ipcRenderer.send('windowresize', this.tmpWidth, this.tmpHeight, false) ipcRenderer.send('windowontop', false) - this.cfg.visual.miniplayer_top_toggle = true; + //this.cfg.visual.miniplayer_top_toggle = true; app.appMode = 'player'; } }, - pinMiniPlayer() { - if (this.cfg.visual.miniplayer_top_toggle) { + pinMiniPlayer(status = false) { + if (!status){ + if (!this.cfg.visual.miniplayer_top_toggle) { ipcRenderer.send('windowontop', true) - this.cfg.visual.miniplayer_top_toggle = false + this.cfg.visual.miniplayer_top_toggle = true; } else { ipcRenderer.send('windowontop', false) - this.cfg.visual.miniplayer_top_toggle = true; + this.cfg.visual.miniplayer_top_toggle = false; + }} else { + ipcRenderer.send('windowontop', this.cfg.visual.miniplayer_top_toggle ?? false) } }, formatTimezoneOffset: (e = new Date) => { diff --git a/src/renderer/views/components/mediaitem-square.ejs b/src/renderer/views/components/mediaitem-square.ejs index 7fcc7dfe..296d1780 100644 --- a/src/renderer/views/components/mediaitem-square.ejs +++ b/src/renderer/views/components/mediaitem-square.ejs @@ -76,7 +76,7 @@ isVisible: false, addedToLibrary: false, guid: this.uuidv4(), - noplay: ["apple-curators"], + noplay: ["apple-curators", "editorial-elements"], nomenu: ["artists", "stations", "apple-curators", "editorial-elements"], app: this.$root, badges: this.$root.socialBadges.badgeMap, diff --git a/src/renderer/views/components/miniplayer.ejs b/src/renderer/views/components/miniplayer.ejs index 6ee98ae4..ca961cac 100644 --- a/src/renderer/views/components/miniplayer.ejs +++ b/src/renderer/views/components/miniplayer.ejs @@ -155,6 +155,9 @@ beforeDestroy() { window.removeEventListener('keyup', this.onEscapeKeyUp) }, + mounted() { + app.pinMiniPlayer(true) + }, methods: { onEscapeKeyUp(event) { if (event.which === 27) { diff --git a/src/renderer/views/pages/settings.ejs b/src/renderer/views/pages/settings.ejs index f8b3d5f2..67be8cb1 100644 --- a/src/renderer/views/pages/settings.ejs +++ b/src/renderer/views/pages/settings.ejs @@ -752,6 +752,15 @@
+
+
+ {{$root.getLz('settings.option.visual.transparent')}}
+ ({{$root.getLz('settings.option.visual.transparent.description')}}) +
+
+ +
+
@@ -909,6 +918,13 @@ updateFields[i].title = "Not available on this type of build"; } } + }, + promptForRelaunch(){ + bootbox.confirm(app.getLz('action.relaunch.confirm'), function (result) { + if (result) { + ipcRenderer.send('relaunchApp',''); + } + }); } } }) From c5d86cd707c4d2af2850f825bd9247f52581d54f Mon Sep 17 00:00:00 2001 From: vapormusic Date: Sat, 5 Mar 2022 22:16:59 +0700 Subject: [PATCH 36/37] disable this for now --- src/main/base/browserwindow.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/base/browserwindow.ts b/src/main/base/browserwindow.ts index 82dc46dd..c415e6bf 100644 --- a/src/main/base/browserwindow.ts +++ b/src/main/base/browserwindow.ts @@ -322,11 +322,12 @@ export class BrowserWindow { this.startWebServer(); BrowserWindow.win = new bw(this.options); - if (process.platform === "win32" && (utils.getStoreValue('visual.transparent') ?? false)) { - var electronVibrancy = require('electron-vibrancy-updated'); - electronVibrancy.SetVibrancy(BrowserWindow.win, 0); + // cant be built in CI + // if (process.platform === "win32" && (utils.getStoreValue('visual.transparent') ?? false)) { + // var electronVibrancy = require('electron-vibrancy-updated'); + // electronVibrancy.SetVibrancy(BrowserWindow.win, 0); - } + // } const ws = new wsapi(BrowserWindow.win) ws.InitWebSockets() // and load the renderer. From d7261dbc86448f7cca66d000e7ebc047492062d5 Mon Sep 17 00:00:00 2001 From: vapormusic Date: Mon, 7 Mar 2022 00:09:43 +0700 Subject: [PATCH 37/37] fixes --- src/main/plugins/chromecast.ts | 2 +- src/renderer/style.less | 24 +++++++++++++++---- src/renderer/views/app/app-navigation.ejs | 5 ++-- .../views/components/animatedartwork-view.ejs | 24 ++++++++++++++++++- src/renderer/views/pages/cider-playlist.ejs | 2 +- 5 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/main/plugins/chromecast.ts b/src/main/plugins/chromecast.ts index 34e48c48..4bdb6113 100644 --- a/src/main/plugins/chromecast.ts +++ b/src/main/plugins/chromecast.ts @@ -41,7 +41,7 @@ export default class ChromecastPlugin { browser.on('ready', browser.discover); browser.on('update', (service :any) => { - if (service.addresses && service.fullname) { + if (service.addresses && service.fullname && service.fullname.includes('_googlecast._tcp')) { this.ondeviceup(service.addresses[0], service.fullname.substring(0, service.fullname.indexOf("._googlecast")) + " " + (service.type[0].description ?? ""), '', 'googlecast'); } }); diff --git a/src/renderer/style.less b/src/renderer/style.less index 75f5279e..ba1f00f6 100644 --- a/src/renderer/style.less +++ b/src/renderer/style.less @@ -1838,6 +1838,9 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb { -webkit-box-orient: vertical; } +.no-animation { + animation: unset !important; +} .fullscreen-view-container { position: absolute; @@ -1877,6 +1880,10 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb { height: 50vh; } + .bg-artwork-container { + display: block !important; + } + @media only screen and (max-width: 1121px) { .display--large { display: flex !important; @@ -1979,13 +1986,10 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb { filter: brightness(85%) saturate(95%) blur(180px) contrast(0.9) opacity(0.9); } - .no-animation { - animation: unset; - } + } } - .lyrics-col { height: 62vh; @@ -2662,6 +2666,10 @@ input[type="range"].web-slider.display--small::-webkit-slider-thumb { width: 200VH; height: 200VH; } + + .bg-artwork-container{ + display: block !important; + } } .lyric-body { @@ -2964,6 +2972,10 @@ body.no-gpu { .bg-artwork-container { display: none; + animation: none !important; + .bg-artwork{ + animation: none !important; + } } #navigation-bar { @@ -3004,6 +3016,10 @@ body.no-gpu { .drawertransition-leave-to { right: -300px; } + + .lyric-line:hover::after { + display: none; + } } .qrimg { diff --git a/src/renderer/views/app/app-navigation.ejs b/src/renderer/views/app/app-navigation.ejs index b7d7f654..58833a1c 100644 --- a/src/renderer/views/app/app-navigation.ejs +++ b/src/renderer/views/app/app-navigation.ejs @@ -6,8 +6,9 @@ v-if="drawer.open && drawer.panel == 'lyrics' && lyrics && lyrics != [] && lyrics.length > 0">
- - + + +
{ + let levelsnum = u.levels.map((level)=>{return level.width}) + if (levelsnum.length > 0) { + let qualities = [] + let qualities2 = [] + for (let i = 0; i < levelsnum.length; i++) { + if (qualities2.indexOf(levelsnum[i]) == -1) { + qualities.push({level: i, quality: levelsnum[i]}) + qualities2.push(levelsnum[i]) + } + } + let actualnum = Math.floor(qualities[qualities.length -1 ].level * (quality / 4)) + if (quality != 0 ){ + quality = qualities[Math.min(actualnum,qualities.length -1 )].level + } + if (quality == 4 ){ + quality = qualities[qualities.length - 1].level + } + } + + this.hls.loadLevel = parseInt( quality || 1);},200) } }) } diff --git a/src/renderer/views/pages/cider-playlist.ejs b/src/renderer/views/pages/cider-playlist.ejs index 32a919d8..c49d03f4 100644 --- a/src/renderer/views/pages/cider-playlist.ejs +++ b/src/renderer/views/pages/cider-playlist.ejs @@ -21,7 +21,7 @@ :video-priority="true" :url="(data.attributes != null && data.attributes.artwork != null) ? data.attributes.artwork.url : ((data.relationships != null && data.relationships.tracks.data.length > 0 && data.relationships.tracks.data[0].attributes != null) ? ((data.relationships.tracks.data[0].attributes.artwork != null)? data.relationships.tracks.data[0].attributes.artwork.url : ''):'')" :video="(data.attributes != null && data.attributes.editorialVideo != null) ? (data.attributes.editorialVideo.motionDetailSquare ? data.attributes.editorialVideo.motionDetailSquare.video : (data.attributes.editorialVideo.motionSquareVideo1x1 ? data.attributes.editorialVideo.motionSquareVideo1x1.video : '')) : '' " - size="260" + size="500" >