From 3693ad6efbdc692460c722129b811c1599622d4c Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Wed, 24 May 2023 17:38:14 +0000 Subject: [PATCH] added `SyncCore` --- packages/app/src/cores/index.js | 3 + packages/app/src/cores/sync/index.js | 301 +++++++++++++++++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 packages/app/src/cores/sync/index.js diff --git a/packages/app/src/cores/index.js b/packages/app/src/cores/index.js index c2803104..c85150ba 100755 --- a/packages/app/src/cores/index.js +++ b/packages/app/src/cores/index.js @@ -15,6 +15,8 @@ import RemoteStorage from "./remoteStorage" import WidgetsCore from "./widgets" +import SyncCore from "./sync" + // DEFINE LOAD ORDER HERE export default [ SettingsCore, @@ -32,4 +34,5 @@ export default [ Player, ContextMenuCore, WidgetsCore, + SyncCore, ] \ No newline at end of file diff --git a/packages/app/src/cores/sync/index.js b/packages/app/src/cores/sync/index.js new file mode 100644 index 00000000..6d55eb03 --- /dev/null +++ b/packages/app/src/cores/sync/index.js @@ -0,0 +1,301 @@ +import React from "react" + +import Core from "evite/src/core" +import EventEmitter from "evite/src/internals/eventBus" + +import SyncRoomCard from "components/SyncRoomCard" +import Image from "components/Image" +import { openModal as OpenUserSelectorModal } from "components/UserSelector" + +// TODO: Sync current state with server +class MusicSyncSubCore { + constructor(ctx) { + this.ctx = ctx + } + + static namespace = "music" + + musicWs = null + + currentRoomData = null + + eventBus = new EventEmitter() + + public = { + joinRoom: this.joinRoom.bind(this), + leaveRoom: this.leaveRoom.bind(this), + dispatchEvent: this.dispatchEvent.bind(this), + inviteToUser: this.inviteToUser.bind(this), + createSyncRoom: this.createSyncRoom.bind(this), + createInviteUserModal: this.createInviteUserModal.bind(this), + currentRoomData: function () { + return this.currentRoomData + }.bind(this), + eventBus: this.eventBus, + } + + hubEvents = { + "invite:received": (data) => { + console.log("invite:received", data) + + app.notification.new({ + title: "Sync", + description: `${data.invitedBy.username} invited you to join a sync room`, + icon: React.createElement(Image, { + src: data.invitedBy.avatar, + }), + }, { + type: "info", + duration: 10000, + actions: [{ + label: "Join", + onClick: () => { + this.joinRoom(data.roomId) + } + }] + }) + }, + "room:joined": (data) => { + console.log("room:joined", data) + + this.currentRoomData = data + + // check if user is owner + app.cores.player.toogleSyncMode(true, data.ownerUserId !== app.userData._id) + + this.eventBus.emit("room:joined", data) + }, + "room:left": (data) => { + console.log("room:left", data) + + this.dettachCard() + + this.currentRoomData = null + + app.cores.player.toogleSyncMode(false, false) + + this.eventBus.emit("room:left", data) + }, + } + + roomEvents = { + "disconnect": () => { + this.dettachCard() + this.currentRoomData = null + + app.notification.new({ + title: "Sync", + description: "Disconnected from sync server" + }, { + type: "error", + }) + + app.cores.player.toogleSyncMode(false, false) + }, + + "room:user:joined": (data) => { + this.eventBus.emit("room:user:joined", data) + }, + "room:user:left": (data) => { + this.eventBus.emit("room:user:left", data) + }, + "room:current-data": (data) => { + console.log("room:current-data", data) + this.currentRoomData = data + + this.eventBus.emit("room:current-data", data) + }, + "room:owner:changed": (data) => { + const isSelf = data.ownerUserId === app.userData._id + + app.cores.player.toogleSyncMode(true, !isSelf) + + this.eventBus.emit("room:owner:changed", data) + }, + // Room Control + "music:player:start": (data) => { + if (data.selfUser.user_id === app.userData._id) { + return false + } + + this.eventBus.emit("music:player:start", data) + + app.cores.player.start(data.manifest, { + sync: true, + }) + }, + "music:player:seek": (data) => { + if (data.selfUser.user_id === app.userData._id) { + return false + } + + this.eventBus.emit("music:player:seek", data) + + app.cores.player.seek(data.position, { + sync: true, + }) + }, + "music:player:status": (data) => { + if (data.selfUser.user_id === app.userData._id) { + return false + } + + // avoid dispatch if event pause and current time is the audio duration + if (data.startingNew || data.status === "paused" && data.time === data.duration) { + return app.cores.player.stop() + } + + switch (data.status) { + case "playing": { + app.cores.player.playback.play() + break + } + case "paused": { + app.cores.player.playback.pause() + break + } + } + } + } + + async onInitialize() { + this.musicWs = this.ctx.CORES.api.instance.wsInstances.music + + Object.keys(this.hubEvents).forEach((eventName) => { + this.musicWs.on(eventName, this.hubEvents[eventName]) + }) + } + + dispatchEvent(eventName, data) { + if (!eventName) { + return false + } + + this.musicWs.emit(eventName, data) + } + + attachCard() { + if (this.syncRoomCardRendered) { + return false + } + + this.syncRoomCardRendered = app.layout.floatingStack.add("sync-room-card", SyncRoomCard) + } + + dettachCard() { + if (!this.syncRoomCardRendered) { + return false + } + + app.layout.floatingStack.remove("sync-room-card") + + this.syncRoomCardRendered = null + } + + joinRoom(roomId, options) { + if (this.currentRoomData) { + console.warn(`Already joined room ${this.currentRoomData}`) + + return false + } + + this.attachCard() + + this.musicWs.emit("join:room", { + room: roomId, + options + }) + + Object.keys(this.roomEvents).forEach((eventName) => { + this.musicWs.on(eventName, this.roomEvents[eventName]) + }) + } + + leaveRoom(roomId) { + this.dettachCard() + + if (!roomId && !this.currentRoomData) { + console.warn(`Not joined any room`) + + return false + } + + if (!roomId) { + roomId = this.currentRoomData.roomId + } + + this.musicWs.emit("leave:room", { + room: roomId + }) + + this.currentRoomData = null + + Object.keys(this.roomEvents).forEach((eventName) => { + this.musicWs.off(eventName, this.roomEvents[eventName]) + }) + } + + inviteToUser(userId) { + if (!this.currentRoomData) { + console.warn(`Not joined any room`) + + return false + } + + this.musicWs.emit("invite:user", { + roomId: this.currentRoomData.roomId, + user_id: userId, + }) + } + + createSyncRoom() { + if (this.currentRoomData) { + console.warn(`Already joined room ${this.currentRoomData}`) + + return false + } + + this.joinRoom(`${app.userData._id}_${Date.now()}`, { + title: `${app.userData.username}'s room`, + }) + + this.createInviteUserModal() + } + + createInviteUserModal() { + //open invite modal + OpenUserSelectorModal({ + onFinished: (selected_ids) => { + console.log("selected_ids", selected_ids) + } + }) + } +} + +export default class SyncCore extends Core { + static refName = "sync" + static namespace = "sync" + static dependencies = ["api", "player"] + + public = {} + + async onInitialize() { + const subCores = [ + new MusicSyncSubCore(this.ctx) + ] + + for await (const subCore of subCores) { + try { + if (typeof subCore.onInitialize === "function") { + await subCore.onInitialize() + } + + if (subCore.constructor.namespace && subCore.public) { + this.public[subCore.constructor.namespace] = subCore.public + } + } catch (error) { + console.error(error) + } + } + } +} \ No newline at end of file