diff --git a/package.json b/package.json
index f7e24a0a..50fa2d19 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,7 @@
"run-script-os": "^1.1.6",
"source-map-support": "^0.5.21",
"v8-compile-cache": "^2.3.0",
- "ws": "^8.4.0",
+ "ws": "^8.4.2",
"xml2js": "^0.4.23",
"youtube-search-without-api-key": "^1.0.7"
},
diff --git a/src/main/base/win.ts b/src/main/base/win.ts
index 292d5562..e27b4145 100644
--- a/src/main/base/win.ts
+++ b/src/main/base/win.ts
@@ -9,6 +9,8 @@ import * as fs from "fs";
import { Stream } from "stream";
import * as qrcode from "qrcode-terminal";
import * as os from "os";
+import {wsapi} from "./wsapi";
+
export class Win {
win: any | undefined = null;
app: any | undefined = null;
@@ -189,6 +191,8 @@ export class Win {
* TODO: Broadcast the remote so that /web-remote/ can connect
* https://github.com/ciderapp/Apple-Music-Electron/blob/818ed18940ff600d76eb59d22016723a75885cd5/resources/functions/handler.js#L1173
*/
+ const ws = new wsapi()
+ ws.InitWebSockets()
const remote = express();
remote.use(express.static(path.join(this.paths.srcPath, "./web-remote/")))
remote.listen(this.remotePort, () => {
diff --git a/src/main/base/wsapi.ts b/src/main/base/wsapi.ts
new file mode 100644
index 00000000..fc346e76
--- /dev/null
+++ b/src/main/base/wsapi.ts
@@ -0,0 +1,291 @@
+// @ts-nocheck
+
+import * as ws from "ws";
+import * as http from "http";
+import * as https from "https";
+import * as url from "url";
+import * as fs from "fs";
+import * as path from "path";
+import * as electron from "electron";
+const WebSocket = ws;
+const WebSocketServer = ws.Server;
+
+private class standardResponse {
+ status: number;
+ message: string;
+ data: any;
+ type: string;
+}
+
+export class wsapi {
+ private standa2rdResponse (status, data, message, type: string = "generic") {
+ this.status = status;
+ this.message = message;
+ this.data = data;
+ this.type = type;
+ }
+
+ port: any = 26369
+ wss: any = null
+ clients: []
+ createId() {
+ // create random guid
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+ var r = Math.random() * 16 | 0,
+ v = c == 'x' ? r : (r & 0x3 | 0x8);
+ return v.toString(16);
+ });
+ }
+ public async InitWebSockets () {
+ electron.ipcMain.on('wsapi-updatePlaybackState', (event, arg) => {
+ wsapi.updatePlaybackState(arg);
+ })
+
+ electron.ipcMain.on('wsapi-returnQueue', (event, arg) => {
+ wsapi.returnQueue(JSON.parse(arg));
+ });
+
+ electron.ipcMain.on('wsapi-returnSearch', (event, arg) => {
+ console.log("SEARCH")
+ wsapi.returnSearch(JSON.parse(arg));
+ });
+
+ electron.ipcMain.on('wsapi-returnSearchLibrary', (event, arg) => {
+ wsapi.returnSearchLibrary(JSON.parse(arg));
+ });
+
+ electron.ipcMain.on('wsapi-returnDynamic', (event, arg, type) => {
+ wsapi.returnDynamic(JSON.parse(arg), type);
+ });
+
+ electron.ipcMain.on('wsapi-returnMusicKitApi', (event, arg, method) => {
+ wsapi.returnMusicKitApi(JSON.parse(arg), method);
+ });
+
+ electron.ipcMain.on('wsapi-returnLyrics', (event, arg) => {
+ wsapi.returnLyrics(JSON.parse(arg));
+ });
+ this.wss = new WebSocketServer({
+ port: this.port,
+ perMessageDeflate: {
+ zlibDeflateOptions: {
+ // See zlib defaults.
+ chunkSize: 1024,
+ memLevel: 7,
+ level: 3
+ },
+ zlibInflateOptions: {
+ chunkSize: 10 * 1024
+ },
+ // Other options settable:
+ clientNoContextTakeover: true, // Defaults to negotiated value.
+ serverNoContextTakeover: true, // Defaults to negotiated value.
+ serverMaxWindowBits: 10, // Defaults to negotiated value.
+ // Below options specified as default values.
+ concurrencyLimit: 10, // Limits zlib concurrency for perf.
+ threshold: 1024 // Size (in bytes) below which messages
+ // should not be compressed if context takeover is disabled.
+ }
+ })
+ console.log(`WebSocketServer started on port: ${this.port}`);
+
+ const defaultResponse = new standardResponse(0, {}, "OK");
+
+
+ this.wss.on('connection', function connection(ws) {
+ ws.id = wsapi.createId();
+ console.log(`Client ${ws.id} connected`)
+ wsapi.clients.push(ws);
+ ws.on('message', function incoming(message) {
+
+ });
+ // ws on message
+ ws.on('message', function incoming(message) {
+ let data = JSON.parse(message);
+ let response = new standardResponse(0, {}, "OK");;
+ if (data.action) {
+ data.action.toLowerCase();
+ }
+ switch (data.action) {
+ default:
+ response.message = "Action not found";
+ break;
+ case "identify":
+ response.message = "Thanks for identifying!"
+ response.data = {
+ id: ws.id
+ }
+ ws.identity = {
+ name: data.name,
+ author: data.author,
+ description: data.description,
+ version: data.version
+ }
+ break;
+ case "play-next":
+ electron.app.win.webContents.executeJavaScript(`wsapi.playNext(\`${data.type}\`,\`${data.id}\`)`);
+ response.message = "Play Next";
+ break;
+ case "play-later":
+ electron.app.win.webContents.executeJavaScript(`wsapi.playLater(\`${data.type}\`,\`${data.id}\`)`);
+ response.message = "Play Later";
+ break;
+ case "quick-play":
+ electron.app.win.webContents.executeJavaScript(`wsapi.quickPlay(\`${data.term}\`)`);
+ response.message = "Quick Play";
+ break;
+ case "get-lyrics":
+ electron.app.win.webContents.executeJavaScript(`wsapi.getLyrics()`);
+ break;
+ case "shuffle":
+ electron.app.win.webContents.executeJavaScript(`wsapi.toggleShuffle()`);
+ break;
+ case "set-shuffle":
+ if(data.shuffle == true) {
+ electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 1`);
+ }else{
+ electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 0`);
+ }
+ break;
+ case "repeat":
+ electron.app.win.webContents.executeJavaScript(`wsapi.toggleRepeat()`);
+ break;
+ case "seek":
+ electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(data.time)})`);
+ response.message = "Seek";
+ break;
+ case "pause":
+ electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().pause()`);
+ response.message = "Paused";
+ break;
+ case "play":
+ electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().play()`);
+ response.message = "Playing";
+ break;
+ case "stop":
+ electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().stop()`);
+ response.message = "Stopped";
+ break;
+ case "volume":
+ electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(data.volume)}`);
+ response.message = "Volume";
+ break;
+ case "mute":
+ electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().mute()`);
+ response.message = "Muted";
+ break;
+ case "unmute":
+ electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().unmute()`);
+ response.message = "Unmuted";
+ break;
+ case "next":
+ electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().skipToNextItem()`);
+ response.message = "Next";
+ break;
+ case "previous":
+ electron.app.win.webContents.executeJavaScript(`MusicKit.getInstance().skipToPreviousItem()`);
+ response.message = "Previous";
+ break;
+ case "musickit-api":
+ electron.app.win.webContents.executeJavaScript(`wsapi.musickitApi(\`${data.method}\`, \`${data.id}\`, ${JSON.stringify(data.params)})`);
+ break;
+ case "musickit-library-api":
+ break;
+ case "set-autoplay":
+ electron.app.win.webContents.executeJavaScript(`wsapi.setAutoplay(${data.autoplay})`);
+ break;
+ case "queue-move":
+ electron.app.win.webContents.executeJavaScript(`wsapi.moveQueueItem(${data.from},${data.to})`);
+ break;
+ case "get-queue":
+ electron.app.win.webContents.executeJavaScript(`wsapi.getQueue()`);
+ break;
+ case "search":
+ if (!data.limit) {
+ data.limit = 10;
+ }
+ electron.app.win.webContents.executeJavaScript(`wsapi.search(\`${data.term}\`, \`${data.limit}\`)`);
+ break;
+ case "library-search":
+ if (!data.limit) {
+ data.limit = 10;
+ }
+ electron.app.win.webContents.executeJavaScript(`wsapi.searchLibrary(\`${data.term}\`, \`${data.limit}\`)`);
+ break;
+ case "show-window":
+ electron.app.win.show()
+ break;
+ case "hide-window":
+ electron.app.win.hide()
+ break;
+ case "play-mediaitem":
+ electron.app.win.webContents.executeJavaScript(`wsapi.playTrackById(${data.id}, \`${data.kind}\`)`);
+ response.message = "Playing track";
+ break;
+ case "get-status":
+ response.data = {
+ isAuthorized: true
+ };
+ response.message = "Status";
+ break;
+ case "get-currentmediaitem":
+ electron.app.win.webContents.executeJavaScript(`wsapi.getPlaybackState()`);
+ break;
+ }
+ ws.send(JSON.stringify(response));
+ });
+
+ ws.on('close', function close() {
+ // remove client from list
+ wsapi.clients.splice(wsapi.clients.indexOf(ws), 1);
+ console.log(`Client ${ws.id} disconnected`);
+ });
+ ws.send(JSON.stringify(defaultResponse));
+ });
+ }
+ sendToClient(id) {
+ // replace the clients.forEach with a filter to find the client that requested
+ }
+ updatePlaybackState(attr) {
+ const response = new standardResponse(0, attr, "OK", "playbackStateUpdate");
+ wsapi.clients.forEach(function each(client) {
+ client.send(JSON.stringify(response));
+ });
+ }
+ returnMusicKitApi(results, method) {
+ const response = new standardResponse(0, results, "OK", `musickitapi.${method}`);
+ wsapi.clients.forEach(function each(client) {
+ client.send(JSON.stringify(response));
+ });
+ }
+ returnDynamic(results, type) {
+ const response = new standardResponse(0, results, "OK", type);
+ wsapi.clients.forEach(function each(client) {
+ client.send(JSON.stringify(response));
+ });
+ }
+ returnLyrics(results) {
+ const response = new standardResponse(0, results, "OK", "lyrics");
+ wsapi.clients.forEach(function each(client) {
+ client.send(JSON.stringify(response));
+ });
+ }
+ returnSearch(results) {
+ const response = new standardResponse(0, results, "OK", "searchResults");
+ wsapi.clients.forEach(function each(client) {
+ client.send(JSON.stringify(response));
+ });
+ }
+ returnSearchLibrary(results) {
+ const response = new standardResponse(0, results, "OK", "searchResultsLibrary");
+ wsapi.clients.forEach(function each(client) {
+ client.send(JSON.stringify(response));
+ });
+ }
+ returnQueue(queue) {
+ const response = new standardResponse(0, queue, "OK", "queue");
+ wsapi.clients.forEach(function each(client) {
+ client.send(JSON.stringify(response));
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/renderer/WSAPI_Interop.js b/src/renderer/WSAPI_Interop.js
new file mode 100644
index 00000000..c7143b37
--- /dev/null
+++ b/src/renderer/WSAPI_Interop.js
@@ -0,0 +1,103 @@
+const wsapi = {
+ cache: {playParams: {id: 0}, status: null, remainingTime: 0},
+ playbackCache: {status: null, time: Date.now()},
+ search(term, limit) {
+ MusicKit.getInstance().api.search(term, {limit: limit, types: 'songs,artists,albums'}).then((results)=>{
+ ipcRenderer.send('wsapi-returnSearch', JSON.stringify(results))
+ })
+ },
+ searchLibrary(term, limit) {
+ MusicKit.getInstance().api.library.search(term, {limit: limit, types: 'library-songs,library-artists,library-albums'}).then((results)=>{
+ ipcRenderer.send('wsapi-returnSearchLibrary', JSON.stringify(results))
+ })
+ },
+ getAttributes: function () {
+ const mk = MusicKit.getInstance();
+ const nowPlayingItem = mk.nowPlayingItem;
+ const isPlayingExport = mk.isPlaying;
+ const remainingTimeExport = mk.currentPlaybackTimeRemaining;
+ const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {});
+
+ attributes.status = isPlayingExport ? isPlayingExport : false;
+ attributes.name = attributes.name ? attributes.name : 'No Title Found';
+ attributes.artwork = attributes.artwork ? attributes.artwork : {url: ''};
+ attributes.artwork.url = attributes.artwork.url ? attributes.artwork.url : '';
+ attributes.playParams = attributes.playParams ? attributes.playParams : {id: 'no-id-found'};
+ attributes.playParams.id = attributes.playParams.id ? attributes.playParams.id : 'no-id-found';
+ attributes.albumName = attributes.albumName ? attributes.albumName : '';
+ attributes.artistName = attributes.artistName ? attributes.artistName : '';
+ attributes.genreNames = attributes.genreNames ? attributes.genreNames : [];
+ attributes.remainingTime = remainingTimeExport ? (remainingTimeExport * 1000) : 0;
+ attributes.durationInMillis = attributes.durationInMillis ? attributes.durationInMillis : 0;
+ attributes.startTime = Date.now();
+ attributes.endTime = attributes.endTime ? attributes.endTime : Date.now();
+ attributes.volume = mk.volume;
+ attributes.shuffleMode = mk.shuffleMode;
+ attributes.repeatMode = mk.repeatMode;
+ attributes.autoplayEnabled = mk.autoplayEnabled;
+ return attributes
+ },
+ moveQueueItem(oldPosition, newPosition) {
+ MusicKit.getInstance().queue._queueItems.splice(newPosition,0,MusicKit.getInstance().queue._queueItems.splice(oldPosition,1)[0])
+ MusicKit.getInstance().queue._reindex()
+ },
+ setAutoplay(value) {
+ MusicKit.getInstance().autoplayEnabled = value
+ },
+ returnDynamic(data, type) {
+ ipcRenderer.send('wsapi-returnDynamic', JSON.stringify(data), type)
+ },
+ musickitApi(method, id, params) {
+ MusicKit.getInstance().api[method](id, params).then((results)=>{
+ ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method)
+ })
+ },
+ getPlaybackState () {
+ ipcRenderer.send('wsapi-updatePlaybackState', MusicKitInterop.getAttributes());
+ },
+ getLyrics() {
+ return []
+ _lyrics.GetLyrics(1, false)
+ },
+ getQueue() {
+ ipcRenderer.send('wsapi-returnQueue', JSON.stringify(MusicKit.getInstance().queue))
+ },
+ playNext(type, id) {
+ var request = {}
+ request[type] = id
+ MusicKit.getInstance().playNext(request)
+ },
+ playLater(type, id) {
+ var request = {}
+ request[type] = id
+ MusicKit.getInstance().playLater(request)
+ },
+ love() {
+
+ },
+ playTrackById(id, kind = "song") {
+ MusicKit.getInstance().setQueue({ [kind]: id }).then(function (queue) {
+ MusicKit.getInstance().play()
+ })
+ },
+ quickPlay(term) {
+ // Quick play by song name
+ MusicKit.getInstance().api.search(term, { limit: 2, types: 'songs' }).then(function (data) {
+ MusicKit.getInstance().setQueue({ song: data["songs"][0]["id"] }).then(function (queue) {
+ MusicKit.getInstance().play()
+ })
+ })
+ },
+ toggleShuffle() {
+ MusicKit.getInstance().shuffleMode = MusicKit.getInstance().shuffleMode === 0 ? 1 : 0
+ },
+ toggleRepeat() {
+ if(MusicKit.getInstance().repeatMode == 0) {
+ MusicKit.getInstance().repeatMode = 2
+ }else if(MusicKit.getInstance().repeatMode == 2){
+ MusicKit.getInstance().repeatMode = 1
+ }else{
+ MusicKit.getInstance().repeatMode = 0
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/renderer/index.js b/src/renderer/index.js
index 61e69fa1..1519c3bf 100644
--- a/src/renderer/index.js
+++ b/src/renderer/index.js
@@ -610,10 +610,16 @@ const app = new Vue({
}
})
+ this.mk.addEventListener(MusicKit.Events.playbackStateDidChange, ()=>{
+ ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes());
+ })
+
this.mk.addEventListener(MusicKit.Events.playbackTimeDidChange, (a) => {
self.lyriccurrenttime = self.mk.currentPlaybackTime
this.currentSongInfo = a
self.playerLCD.playbackDuration = (self.mk.currentPlaybackTime)
+ // wsapi
+ ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes());
})
this.mk.addEventListener(MusicKit.Events.nowPlayingItemDidChange, (a) => {
diff --git a/src/renderer/views/main.ejs b/src/renderer/views/main.ejs
index c952abf6..242b5337 100644
--- a/src/renderer/views/main.ejs
+++ b/src/renderer/views/main.ejs
@@ -710,5 +710,6 @@
+