diff --git a/.github/workflows/cider-chores.yml b/.github/workflows/cider-chores.yml index 7daa90e4..dfb3eb59 100644 --- a/.github/workflows/cider-chores.yml +++ b/.github/workflows/cider-chores.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: # Make sure the actual branch is checked out when running on pull requests ref: ${{ github.head_ref }} @@ -55,7 +55,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: # Make sure the actual branch is checked out when running on pull requests ref: ${{ github.head_ref }} @@ -69,7 +69,31 @@ jobs: commit_message: "chore: Prettified Code\n [ci skip]" commit_user_name: "cider-chore[bot]" commit_user_email: "cider-chore[bot]@users.noreply.github.com" + + update-i18n-source: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18] + + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Update Source Files + run: cp src/i18n/en_US.json src/i18n/source/en_US.json + + - name: Commit Updated Source File + uses: stefanzweifel/git-auto-commit-action@v4.14.1 + with: + commit_message: "chore: Updated i18n Source\n [ci skip]" + commit_user_name: "cider-chore[bot]" + commit_user_email: "cider-chore[bot]@users.noreply.github.com" + synchronize-with-crowdin: runs-on: ubuntu-latest if: ${{ false }} # disable for now @@ -82,7 +106,11 @@ jobs: uses: crowdin/github-action@1.4.13 with: upload_translations: true - download_translations: true + download_translations: false + project_id: ${{ secrets.CROWDIN_PROJECT_ID }} + token: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + source: '/src/i18n/source/**.*' + translation: '/src/i18n/%locale_with_underscore%.json' env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} diff --git a/.github/workflows/pr-chores.yml b/.github/workflows/pr-chores.yml index 0abc6dd4..66e14ae9 100644 --- a/.github/workflows/pr-chores.yml +++ b/.github/workflows/pr-chores.yml @@ -33,6 +33,5 @@ jobs: - name: Run linter 👀 uses: wearerequired/lint-action@v2 with: - eslint: true prettier: true - prettier_args: "'**/*.{js,json,ts,css,vue,less}'" + prettier_args: "'src/**/*.{js,json,ts,css,vue,less}'" diff --git a/README.md b/README.md index a5c70150..c4cb84c6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

GitHub Stars GitHub Forks - +
Buy Me A Coffee Open Collective diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 00000000..3ce3bcbd --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /src/i18n/source/en_US.json + translation: /src/i18n/%locale_with_underscore%.json diff --git a/resources/version.sh b/resources/version.sh index 3b657cb8..769cbeaa 100755 --- a/resources/version.sh +++ b/resources/version.sh @@ -9,8 +9,9 @@ else STABLE_SHA=$(curl -s https://api.github.com/repos/ciderapp/Cider/branches/stable | grep '"sha"' | head -1 | cut -d '"' -f 4) fi + SHA_DATE=$(git show -s --format=%ci $STABLE_SHA) -COMMIT_SINCE_STABLE=$(git rev-list $STABLE_SHA..HEAD --count --since="$SHA_DATE") +COMMIT_SINCE_STABLE=$(printf "%03d\n" $(git rev-list $STABLE_SHA..HEAD --count --since="$SHA_DATE")) CURRENT_VERSION=$(node -p -e "require('./package.json').version") # Set the version number for commits on main branch diff --git a/src/main/base/browserwindow.ts b/src/main/base/browserwindow.ts index 3e06e7d2..c044894a 100644 --- a/src/main/base/browserwindow.ts +++ b/src/main/base/browserwindow.ts @@ -479,7 +479,7 @@ export class BrowserWindow { } } for (let i = 0; i < expectedFiles.length; i++) { - const file = join(utils.getPath("ciderCache"), expectedFiles[i]); + const file = join(join(app.getPath("userData"), "CiderCache"), expectedFiles[i]); if (!existsSync(file)) { writeFileSync(file, JSON.stringify([])); } @@ -1067,13 +1067,13 @@ export class BrowserWindow { }); ipcMain.handle("put-cache", (_event, arg) => { - writeFileSync(join(utils.getPath("ciderCache"), `${arg.file}.json`), arg.data); + writeFileSync(join(join(app.getPath("userData"), "CiderCache"), `${arg.file}.json`), arg.data); }); ipcMain.on("get-cache", (event, arg) => { let read = ""; - if (existsSync(join(utils.getPath("ciderCache"), `${arg}.json`))) { - read = readFileSync(join(utils.getPath("ciderCache"), `${arg}.json`), "utf8"); + if (existsSync(join(join(app.getPath("userData"), "CiderCache"), `${arg}.json`))) { + read = readFileSync(join(join(app.getPath("userData"), "CiderCache"), `${arg}.json`), "utf8"); } event.returnValue = read; }); diff --git a/src/main/base/store.ts b/src/main/base/store.ts index a2a98363..bb6383c8 100644 --- a/src/main/base/store.ts +++ b/src/main/base/store.ts @@ -1,9 +1,6 @@ import * as ElectronStore from "electron-store"; import { app, ipcMain } from "electron"; import fetch from "electron-fetch"; -import { existsSync } from "fs"; -import { join } from "path"; -import { utils } from "./utils"; export class Store { static cfg: ElectronStore; @@ -122,7 +119,7 @@ export class Store { }, audio: { volume: 1, - volumeStep: 0.05, + volumeStep: 0.01, maxVolume: 1, lastVolume: 1, muted: false, @@ -231,6 +228,11 @@ export class Store { settings: false, }, }, + musickit: { + "stored-attributes": { + autoplayEnabled: "", + }, + }, }; private migrations: any = {}; private schema: ElectronStore.Schema = { @@ -331,8 +333,4 @@ export class Store { Store.cfg.store = store; }); } - - private checkLocale(language: string) { - return existsSync(join(utils.getPath("i18nPath"), `${language}.json`)) ? language : "en_US"; - } } diff --git a/src/main/base/wsapi.ts b/src/main/base/wsapi.ts index d161b279..06cc632e 100644 --- a/src/main/base/wsapi.ts +++ b/src/main/base/wsapi.ts @@ -200,15 +200,11 @@ export class wsapi { response.message = "Unmuted"; break; case "next": - this._win.webContents.executeJavaScript(`if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null) { - try { - app.prevButtonBackIndicator = false; - } catch (e) { } - MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex);}`); + this._win.webContents.executeJavaScript(`MusicKitInterop.next()`); response.message = "Next"; break; case "previous": - this._win.webContents.executeJavaScript(`if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex)}`); + this._win.webContents.executeJavaScript(`MusicKitInterop.previous()`); response.message = "Previous"; break; case "musickit-api": diff --git a/src/main/plugins/lastfm.ts b/src/main/plugins/lastfm.ts index bfceeea3..06fd5c42 100644 --- a/src/main/plugins/lastfm.ts +++ b/src/main/plugins/lastfm.ts @@ -50,17 +50,17 @@ export default class lastfm { }); this._utils.getIPCMain().on("lastfm:nowPlayingChange", (event: any, attributes: any) => { - if (this._utils.getStoreValue("connectivity.lastfm.filter_loop") || this._utils.getStoreValue("general.privateEnabled") || attributes.type === "radioStation") return; + if (this._utils.getStoreValue("connectivity.lastfm.filter_loop") || this._utils.getStoreValue("general.privateEnabled") || attributes.kind === "radioStation") return; this.updateNowPlayingTrack(attributes); }); this._utils.getIPCMain().on("lastfm:FilteredNowPlayingItemDidChange", (event: any, attributes: any) => { - if (this._utils.getStoreValue("general.privateEnabled") || attributes.type === "radioStation") return; + if (this._utils.getStoreValue("general.privateEnabled") || attributes.kind === "radioStation") return; this.updateNowPlayingTrack(attributes); }); this._utils.getIPCMain().on("lastfm:scrobbleTrack", (event: any, attributes: any) => { - if (this._utils.getStoreValue("general.privateEnabled") || attributes.type === "radioStation") return; + if (this._utils.getStoreValue("general.privateEnabled") || attributes.kind === "radioStation") return; this.scrobbleTrack(attributes); }); } @@ -170,7 +170,7 @@ export default class lastfm { return; } - if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.type] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._scrobbleCache.track === attributes.lfmTrack.name)) return; + if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.kind] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._scrobbleCache.track === attributes.lfmTrack.name)) return; // Scrobble const scrobble = { @@ -209,8 +209,8 @@ export default class lastfm { }); return; } - - if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.type] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._nowPlayingCache.track === attributes.lfmTrack.name)) return; + + if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.kind] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._nowPlayingCache.track === attributes.lfmTrack.name)) return; console.log(this._utils.getStoreValue("connectivity.lastfm.filter_types")); diff --git a/src/preload/cider-preload.js b/src/preload/cider-preload.js index ba968b2f..afc3cf74 100644 --- a/src/preload/cider-preload.js +++ b/src/preload/cider-preload.js @@ -10,6 +10,7 @@ const MusicKitInterop = { MusicKit.getInstance().addEventListener(MusicKit.Events.playbackStateDidChange, () => { const attributes = MusicKitInterop.getAttributes(); if (!attributes) return; + MusicKitInterop.updateMediaState(attributes); if (MusicKitInterop.filterTrack(attributes, true, false)) { global.ipcRenderer.send("playbackStateDidChange", attributes); global.ipcRenderer.send("wsapi-updatePlaybackState", attributes); @@ -35,6 +36,7 @@ const MusicKitInterop = { const attributes = MusicKitInterop.getAttributes(); if (!attributes) return; ipcRenderer.send("playbackTimeDidChange", attributes); + MusicKitInterop.updatePositionState(attributes); }); /* MusicKit.Events.nowPlayingItemDidChange */ @@ -43,6 +45,7 @@ const MusicKitInterop = { if (!attributes) return; attributes.primaryArtist = app.cfg.connectivity.lastfm.remove_featured ? await this.fetchSongRelationships() : attributes.artistName; + MusicKitInterop.updateMediaSession(attributes); global.ipcRenderer.send("nowPlayingItemDidChange", attributes); if (MusicKitInterop.filterTrack(attributes, false, true)) { @@ -141,8 +144,8 @@ const MusicKitInterop = { const attributes = nowPlayingItem != null ? nowPlayingItem.attributes : {}; attributes.songId = attributes.songId ?? attributes.playParams?.catalogId ?? attributes.playParams?.id; - attributes.type = nowPlayingItem?.type ?? ""; - attributes.status = isPlayingExport ?? null; + attributes.kind = nowPlayingItem?.type ?? attributes.type ?? attributes.playParams.kind ?? ""; + attributes.status = nowPlayingItem == null ? null : !!isPlayingExport; attributes.name = attributes?.name ?? "no-title-found"; attributes.artwork = attributes?.artwork ?? { url: "" }; attributes.artwork.url = (attributes?.artwork?.url ?? "").replace(`{f}`, "png"); @@ -205,22 +208,139 @@ const MusicKitInterop = { }, next: () => { - // try { - // app.prevButtonBackIndicator = false; - // } catch (e) { } - // if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null) - // MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex); - MusicKit.getInstance() - .skipToNextItem() - .then((r) => console.debug(`[cider:preload] [next] Skipping to Next ${r}`)); + if (app) { + app.skipToNextItem(); + } else { + MusicKit.getInstance() + .skipToNextItem() + .then((r) => console.debug(`[cider:preload] [next] Skipping to Next ${r}`)); + } }, previous: () => { - // if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) - // MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex); - MusicKit.getInstance() - .skipToPreviousItem() - .then((r) => console.debug(`[cider:preload] [previous] Skipping to Previous ${r}`)); + if (app) { + app.skipToPreviousItem(); + } else { + MusicKit.getInstance() + .skipToPreviousItem() + .then((r) => console.debug(`[cider:preload] [previous] Skipping to Previous ${r}`)); + } + }, + + initMediaSession: () => { + if ("mediaSession" in navigator) { + const defaultSkipTime = 10; + + console.debug("[cider:preload] [initMediaSession] Media Session API supported"); + navigator.mediaSession.setActionHandler("play", () => { + MusicKitInterop.play(); + console.log("[cider:preload] [initMediaSession] Play"); + }); + navigator.mediaSession.setActionHandler("pause", () => { + MusicKitInterop.pause(); + console.log("[cider:preload] [initMediaSession] Pause"); + }); + navigator.mediaSession.setActionHandler("stop", () => { + MusicKit.getInstance().stop(); + console.log("[cider:preload] [initMediaSession] Stop"); + }); + navigator.mediaSession.setActionHandler("seekbackward", (details) => { + const skipTime = details.seekOffset || defaultSkipTime; + MusicKit.getInstance().seekToTime(Math.max(MusicKit.getInstance().currentPlaybackTime - skipTime, 0)); + console.log(`[cider:preload] [initMediaSession] Seek Backward ${skipTime}`); + }); + navigator.mediaSession.setActionHandler("seekforward", (details) => { + const skipTime = details.seekOffset || defaultSkipTime; + MusicKit.getInstance().seekToTime(Math.max(MusicKit.getInstance().currentPlaybackTime + skipTime, 0)); + console.log(`[cider:preload] [initMediaSession] Seek Forward ${skipTime}`); + }); + navigator.mediaSession.setActionHandler("seekto", ({ seekTime, fastSeek }) => { + MusicKit.getInstance().seekToTime(seekTime); + console.log(`[cider:preload] [initMediaSession] Seek To ${seekTime}`); + }); + navigator.mediaSession.setActionHandler("previoustrack", () => { + MusicKitInterop.previous(); + console.log("[cider:preload] [initMediaSession] Previous Track"); + }); + navigator.mediaSession.setActionHandler("nexttrack", () => { + MusicKitInterop.next(); + console.log("[cider:preload] [initMediaSession] Next Track"); + }); + } else { + console.debug("[cider:preload] [initMediaSession] Media Session API not supported"); + } + }, + + updateMediaSession: (a) => { + if ("mediaSession" in navigator) { + navigator.mediaSession.metadata = new MediaMetadata({ + title: a.name, + artist: a.artistName, + album: a.albumName, + artwork: [ + { + src: a.artwork.url.replace("/{w}x{h}bb", "/96x96bb").replace("/2000x2000bb", "/35x35bb"), + sizes: "96x96", + type: "image/jpeg", + }, + { + src: a.artwork.url.replace("/{w}x{h}bb", "/128x128bb").replace("/2000x2000bb", "/35x35bb"), + sizes: "128x128", + type: "image/jpeg", + }, + { + src: a.artwork.url.replace("/{w}x{h}bb", "/192x192bb").replace("/2000x2000bb", "/35x35bb"), + sizes: "192x192", + type: "image/jpeg", + }, + { + src: a.artwork.url.replace("/{w}x{h}bb", "/256x256bb").replace("/2000x2000bb", "/35x35bb"), + sizes: "256x256", + type: "image/jpeg", + }, + { + src: a.artwork.url.replace("/{w}x{h}bb", "/384x384bb").replace("/2000x2000bb", "/35x35bb"), + sizes: "384x384", + type: "image/jpeg", + }, + { + src: a.artwork.url.replace("/{w}x{h}bb", "/512x512bb").replace("/2000x2000bb", "/35x35bb"), + sizes: "512x512", + type: "image/jpeg", + }, + ], + }); + } + }, + + updateMediaState: (a) => { + if ("mediaSession" in navigator) { + console.log("[cider:preload] [updateMediaState] Updating Media State to " + a.status); + switch (a.status) { + default: + case null: + navigator.mediaSession.playbackState = "none"; + break; + + case false: + navigator.mediaSession.playbackState = "paused"; + break; + + case true: + navigator.mediaSession.playbackState = "playing"; + break; + } + } + }, + + updatePositionState: (a) => { + if ("mediaSession" in navigator && a.currentPlaybackTime <= a.durationInMillis / 1000 && a.currentPlaybackTime >= 0) { + navigator.mediaSession.setPositionState({ + duration: a.durationInMillis / 1000, + playbackRate: app?.cfg?.audio?.playbackRate ?? 1, + position: a.currentPlaybackTime, + }); + } }, }; diff --git a/src/renderer/main/vueapp.js b/src/renderer/main/vueapp.js index 1bf457d5..49b3606c 100644 --- a/src/renderer/main/vueapp.js +++ b/src/renderer/main/vueapp.js @@ -195,7 +195,6 @@ const app = new Vue({ type: "", }, MVsource: null, - prevButtonBackIndicator: false, currentSongInfo: {}, page: "", pageHistory: [], @@ -292,6 +291,21 @@ const app = new Vue({ setWindowHash(route = "") { window.location.hash = `#${route}`; }, + monitorMusickit() { + if (!app.cfg.musickit) return; + + for (const [attr, value] of Object.entries(app.cfg.musickit["stored-attributes"])) { + console.log(`Musickit value: ` + app.mk[attr]); + console.log(`Config value: ` + value); + if (value !== "" && app.mk[attr] !== value) { + app.mk[attr] = value; + } + this.$watch(`mk.${attr}`, (val) => { + console.log(`MK ${attr} changed to ${val}`); + app.cfg.musickit["stored-attributes"][attr] = val; + }); + } + }, async oobeInit() { this.appMode = "oobe"; for (const [k, v] of Object.entries(ipcRenderer.sendSync("get-i18n-listing"))) { @@ -826,6 +840,7 @@ const app = new Vue({ }; } MusicKitInterop.init(); + this.monitorMusickit(); // Set the volume // Check the value of this.cfg.audio.muted @@ -2233,40 +2248,20 @@ const app = new Vue({ } }, prevButton() { - if (!app.prevButtonBackIndicator && app.mk.nowPlayingItem && app.mk.currentPlaybackTime > 2) { - app.prevButtonBackIndicator = true; - try { - clearTimeout(app.pauseButtonTimer); - } catch (e) {} + if (app.mk.nowPlayingItem && app.mk.currentPlaybackTime > 2) { app.mk.seekToTime(0); - app.pauseButtonTimer = setTimeout(() => { - app.prevButtonBackIndicator = false; - }, 3000); } else { - try { - clearTimeout(app.pauseButtonTimer); - } catch (e) {} - app.prevButtonBackIndicator = false; app.skipToPreviousItem(); } }, isDisabled() { - if (!app.mk.nowPlayingItem || app.mk.nowPlayingItem.attributes.playParams.kind == "radioStation") { - return true; - } - return false; + return !app.mk.nowPlayingItem || app.mk.nowPlayingItem.attributes.playParams.kind === "radioStation"; }, isPrevDisabled() { - if (this.isDisabled() || (app.mk.queue._position == 0 && app.mk.currentPlaybackTime <= 2)) { - return true; - } - return false; + return this.isDisabled() || (app.mk.queue._position === 0 && app.mk.currentPlaybackTime <= 2); }, isNextDisabled() { - if (this.isDisabled() || app.mk.queue._position + 1 == app.mk.queue.length) { - return true; - } - return false; + return this.isDisabled() || app.mk.queue._position + 1 === app.mk.queue.length; }, async getNowPlayingItemDetailed(target) { @@ -5082,21 +5077,19 @@ const app = new Vue({ } }, 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); + 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); + 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(); - }); + MusicKitInterop.initMediaSession(); + // navigator.mediaSession.setActionHandler("previoustrack", function () { + // app.skipToPreviousItem(); + // }); + // navigator.mediaSession.setActionHandler("nexttrack", function () { + // app.skipToNextItem(); + // }); }, authCC() { ipcRenderer.send("cc-auth"); diff --git a/src/renderer/views/pages/oobe.ejs b/src/renderer/views/pages/oobe.ejs index e4486f35..88057756 100644 --- a/src/renderer/views/pages/oobe.ejs +++ b/src/renderer/views/pages/oobe.ejs @@ -153,7 +153,9 @@