mirror of
https://github.com/moeru-ai/airi.git
synced 2026-07-09 15:58:27 +00:00
fix(stage-tamagotchi,stage-shared): correctly route beat-sync events
This commit is contained in:
parent
078e604795
commit
c9d148f992
7 changed files with 64 additions and 35 deletions
|
|
@ -19,7 +19,7 @@ import { openDebugger, setupDebugger } from './app/debugger'
|
|||
import { emitAppBeforeQuit, emitAppReady, emitAppWindowAllClosed, onAppBeforeQuit } from './libs/bootkit/lifecycle'
|
||||
import { setElectronMainDirname } from './libs/electron/location'
|
||||
import { setupChannelServer } from './services/airi/channel-server'
|
||||
import { setupBeatSyncBackgroundWindow } from './windows/beat-sync'
|
||||
import { setupBeatSync } from './windows/beat-sync'
|
||||
import { setupCaptionWindowManager } from './windows/caption'
|
||||
import { setupChatWindowReusableFunc } from './windows/chat'
|
||||
import { setupInlayWindow } from './windows/inlay'
|
||||
|
|
@ -77,7 +77,7 @@ function setupTray(params: {
|
|||
settingsWindow: () => Promise<BrowserWindow>
|
||||
captionWindow: ReturnType<typeof setupCaptionWindowManager>
|
||||
widgetsWindow: WidgetsWindowManager
|
||||
beatSyncBackgroundWindow: BrowserWindow
|
||||
beatSyncBgWindow: BrowserWindow
|
||||
}): void {
|
||||
once(() => {
|
||||
const trayImage = nativeImage.createFromPath(isMacOS ? macOSTrayIcon : icon).resize({ width: 16 })
|
||||
|
|
@ -105,7 +105,7 @@ function setupTray(params: {
|
|||
...is.dev || env.MAIN_APP_DEBUG || env.APP_DEBUG
|
||||
? [
|
||||
{ type: 'header', label: 'DevTools' },
|
||||
{ label: 'Troubleshoot BeatSync...', click: () => params.beatSyncBackgroundWindow.webContents.openDevTools() },
|
||||
{ label: 'Troubleshoot BeatSync...', click: () => params.beatSyncBgWindow.webContents.openDevTools() },
|
||||
{ type: 'separator' },
|
||||
] as const // :(
|
||||
: [],
|
||||
|
|
@ -130,26 +130,38 @@ app.whenReady().then(async () => {
|
|||
const widgetsManager = injeca.provide('windows:widgets', { build: () => setupWidgetsWindowManager() })
|
||||
const noticeWindow = injeca.provide('windows:notice', { build: () => setupNoticeWindowManager() })
|
||||
|
||||
// This is a background window
|
||||
const beatSyncBackgroundWindow = injeca.provide('windows:beat-sync', {
|
||||
build: () => setupBeatSyncBackgroundWindow(),
|
||||
// BeatSync will create a background window to capture and process audio.
|
||||
const beatSync = injeca.provide('windows:beat-sync', {
|
||||
build: () => setupBeatSync(),
|
||||
})
|
||||
|
||||
const settingsWindow = injeca.provide('windows:settings', {
|
||||
dependsOn: { widgetsManager, beatSyncBackgroundWindow },
|
||||
build: ({ dependsOn }) => setupSettingsWindowReusableFunc(dependsOn),
|
||||
dependsOn: { widgetsManager, beatSync },
|
||||
build: async ({ dependsOn }) => async () => {
|
||||
const { beatSync: _, ...setupArgs } = dependsOn
|
||||
const window = await setupSettingsWindowReusableFunc(setupArgs)()
|
||||
dependsOn.beatSync.dispatchTo(window)
|
||||
return window
|
||||
},
|
||||
})
|
||||
const mainWindow = injeca.provide('windows:main', {
|
||||
dependsOn: { settingsWindow, chatWindow, widgetsManager, noticeWindow, beatSyncBackgroundWindow },
|
||||
build: async ({ dependsOn }) => setupMainWindow(dependsOn),
|
||||
dependsOn: { settingsWindow, chatWindow, widgetsManager, noticeWindow, beatSync },
|
||||
build: async ({ dependsOn }) => {
|
||||
const { beatSync: _, ...setupArgs } = dependsOn
|
||||
const window = await setupMainWindow(setupArgs)
|
||||
dependsOn.beatSync.dispatchTo(window)
|
||||
return window
|
||||
},
|
||||
})
|
||||
const captionWindow = injeca.provide('windows:caption', {
|
||||
dependsOn: { mainWindow },
|
||||
build: async ({ dependsOn }) => setupCaptionWindowManager(dependsOn),
|
||||
})
|
||||
const tray = injeca.provide('app:tray', {
|
||||
dependsOn: { mainWindow, settingsWindow, captionWindow, widgetsWindow: widgetsManager, beatSyncBackgroundWindow },
|
||||
build: async ({ dependsOn }) => setupTray(dependsOn),
|
||||
dependsOn: { mainWindow, settingsWindow, captionWindow, widgetsWindow: widgetsManager, beatSync },
|
||||
build: async ({ dependsOn }) => {
|
||||
const { beatSync, ...setupArgs } = dependsOn
|
||||
return setupTray({ ...setupArgs, beatSyncBgWindow: beatSync.window })
|
||||
},
|
||||
})
|
||||
injeca.invoke({
|
||||
dependsOn: { mainWindow, tray, channelServerModule },
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import { dirname, join, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { BrowserWindow } from 'electron'
|
||||
import { defineInvoke, defineInvokeHandler } from '@moeru/eventa'
|
||||
import { createContext } from '@moeru/eventa/adapters/electron/main'
|
||||
import { beatSyncRequestSignalBeat, beatSyncSignalBeat } from '@proj-airi/stage-shared'
|
||||
import { BrowserWindow, ipcMain } from 'electron'
|
||||
|
||||
import { baseUrl, getElectronMainDirname, load } from '../../libs/electron/location'
|
||||
|
||||
export async function setupBeatSyncBackgroundWindow() {
|
||||
export async function setupBeatSync() {
|
||||
const window = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
|
|
@ -13,6 +16,16 @@ export async function setupBeatSyncBackgroundWindow() {
|
|||
sandbox: false,
|
||||
},
|
||||
})
|
||||
|
||||
await load(window, baseUrl(resolve(getElectronMainDirname(), '..', 'renderer'), 'beat-sync.html'))
|
||||
return window
|
||||
return {
|
||||
window,
|
||||
dispatchTo: (window: BrowserWindow) => {
|
||||
const context = createContext(ipcMain, window).context
|
||||
const signalBeat = defineInvoke(context, beatSyncSignalBeat)
|
||||
const removeHandler = defineInvokeHandler(context, beatSyncRequestSignalBeat, async e => signalBeat(e))
|
||||
window.on('closed', () => removeHandler())
|
||||
return removeHandler
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import { defineInvoke, defineInvokeHandler } from '@moeru/eventa'
|
||||
import { createContext } from '@moeru/eventa/adapters/electron/renderer'
|
||||
import { createDetector, StageEnvironment } from '@proj-airi/stage-shared'
|
||||
|
||||
import { beatSyncSignalBeat, beatSyncToggle } from '../shared/eventa'
|
||||
import { beatSyncRequestSignalBeat, beatSyncToggle, createBeatSyncDetector, StageEnvironment } from '@proj-airi/stage-shared'
|
||||
|
||||
const { ipcRenderer } = window.electron
|
||||
|
||||
const context = createContext(ipcRenderer).context
|
||||
const signalBeat = defineInvoke(context, beatSyncSignalBeat)
|
||||
const requestSignalBeat = defineInvoke(context, beatSyncRequestSignalBeat)
|
||||
|
||||
function enableLoopbackAudio() {
|
||||
// electron-audio-loopback currently registers this handler internally
|
||||
|
|
@ -19,13 +17,13 @@ function disableLoopbackAudio() {
|
|||
return ipcRenderer.invoke('disable-loopback-audio')
|
||||
}
|
||||
|
||||
const detector = createDetector({
|
||||
const detector = createBeatSyncDetector({
|
||||
env: StageEnvironment.Tamagotchi,
|
||||
enableLoopbackAudio,
|
||||
disableLoopbackAudio,
|
||||
})
|
||||
|
||||
detector.on('beat', () => signalBeat())
|
||||
detector.on('beat', e => requestSignalBeat(e))
|
||||
|
||||
defineInvokeHandler(context, beatSyncToggle, async (enabled) => {
|
||||
if (enabled) {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,4 @@ export const widgetsRemoveEvent = defineEventa<{ id: string }>('eventa:event:ele
|
|||
export const widgetsClearEvent = defineEventa('eventa:event:electron:windows:widgets:clear')
|
||||
export const widgetsUpdateEvent = defineEventa<{ id: string, componentProps?: Record<string, any> }>('eventa:event:electron:windows:widgets:update')
|
||||
|
||||
export const beatSyncToggle = defineInvokeEventa<void, boolean>('eventa:event:electron:beat-sync:toggle')
|
||||
export const beatSyncSignalBeat = defineInvokeEventa<void, void>('eventa:event:electron:beat-sync:signal-beat')
|
||||
|
||||
export { electron } from './electron'
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/preload": "^3.0.2",
|
||||
"@moeru/eventa": "^0.3.0",
|
||||
"@nekopaw/tempora": "0.3.1-alpha.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,29 +2,30 @@ import type { Analyser, AnalyserBeatEvent, AnalyserWorkletParameters } from '@ne
|
|||
|
||||
import analyserWorklet from '@nekopaw/tempora/worklet?url'
|
||||
|
||||
import { defineInvokeEventa } from '@moeru/eventa'
|
||||
import { startAnalyser as startTemporaAnalyser } from '@nekopaw/tempora'
|
||||
|
||||
import { StageEnvironment } from './environment'
|
||||
|
||||
export interface DetectorEventMap {
|
||||
export interface BeatSyncDetectorEventMap {
|
||||
stateChange: (isActive: boolean) => void
|
||||
beat: (e: AnalyserBeatEvent) => void
|
||||
}
|
||||
|
||||
export interface Detector {
|
||||
export interface BeatSyncDetector {
|
||||
start: (createSource: (context: AudioContext) => Promise<AudioNode>) => Promise<void>
|
||||
updateParameters: (params: Partial<AnalyserWorkletParameters>) => void
|
||||
startScreenCapture: () => Promise<void>
|
||||
stop: () => void
|
||||
on: <E extends keyof DetectorEventMap>(event: E, listener: DetectorEventMap[E]) => void
|
||||
off: <E extends keyof DetectorEventMap>(event: E, listener: DetectorEventMap[E]) => void
|
||||
on: <E extends keyof BeatSyncDetectorEventMap>(event: E, listener: BeatSyncDetectorEventMap[E]) => void
|
||||
off: <E extends keyof BeatSyncDetectorEventMap>(event: E, listener: BeatSyncDetectorEventMap[E]) => void
|
||||
readonly isActive: boolean
|
||||
readonly context: AudioContext | undefined
|
||||
readonly analyser: Analyser | undefined
|
||||
readonly source: AudioNode | undefined
|
||||
}
|
||||
|
||||
export type CreateDetectorOptions = |
|
||||
export type CreateBeatSyncDetectorOptions = |
|
||||
{
|
||||
env: StageEnvironment.Tamagotchi
|
||||
enableLoopbackAudio: () => Promise<any>
|
||||
|
|
@ -34,7 +35,7 @@ export type CreateDetectorOptions = |
|
|||
env: StageEnvironment.Web
|
||||
}
|
||||
|
||||
export function createDetector(options: CreateDetectorOptions): Detector {
|
||||
export function createBeatSyncDetector(options: CreateBeatSyncDetectorOptions): BeatSyncDetector {
|
||||
let context: AudioContext | undefined
|
||||
let analyser: Analyser | undefined
|
||||
let source: AudioNode | undefined
|
||||
|
|
@ -42,12 +43,12 @@ export function createDetector(options: CreateDetectorOptions): Detector {
|
|||
|
||||
let stopSource: (() => void) | undefined
|
||||
|
||||
const listeners: { [K in keyof DetectorEventMap]: Array<(...args: any) => void> } = {
|
||||
const listeners: { [K in keyof BeatSyncDetectorEventMap]: Array<(...args: any) => void> } = {
|
||||
stateChange: [],
|
||||
beat: [],
|
||||
}
|
||||
|
||||
const emit = <E extends keyof DetectorEventMap>(event: E, ...args: Parameters<DetectorEventMap[E]>) => {
|
||||
const emit = <E extends keyof BeatSyncDetectorEventMap>(event: E, ...args: Parameters<BeatSyncDetectorEventMap[E]>) => {
|
||||
listeners[event].forEach(listener => listener(...args))
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +162,7 @@ export function createDetector(options: CreateDetectorOptions): Detector {
|
|||
updateParameters,
|
||||
startScreenCapture,
|
||||
stop,
|
||||
on: <E extends keyof DetectorEventMap>(event: E, listener: DetectorEventMap[E]) => {
|
||||
on: <E extends keyof BeatSyncDetectorEventMap>(event: E, listener: BeatSyncDetectorEventMap[E]) => {
|
||||
switch (event) {
|
||||
case 'beat':
|
||||
listeners.beat.push(listener)
|
||||
|
|
@ -170,7 +171,7 @@ export function createDetector(options: CreateDetectorOptions): Detector {
|
|||
throw new Error(`Unknown event: ${event}`)
|
||||
}
|
||||
},
|
||||
off: (event, listener) => {
|
||||
off: <E extends keyof BeatSyncDetectorEventMap>(event: E, listener: BeatSyncDetectorEventMap[E]) => {
|
||||
switch (event) {
|
||||
case 'beat': {
|
||||
const index = listeners.beat.indexOf(listener)
|
||||
|
|
@ -189,3 +190,7 @@ export function createDetector(options: CreateDetectorOptions): Detector {
|
|||
get source() { return source },
|
||||
}
|
||||
}
|
||||
|
||||
export const beatSyncToggle = defineInvokeEventa<void, boolean>('eventa:event:electron:beat-sync:toggle')
|
||||
export const beatSyncRequestSignalBeat = defineInvokeEventa<void, AnalyserBeatEvent>('eventa:event:electron:beat-sync:request-signal-beat')
|
||||
export const beatSyncSignalBeat = defineInvokeEventa<void, AnalyserBeatEvent>('eventa:event:electron:beat-sync:signal-beat')
|
||||
|
|
|
|||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
|
|
@ -1337,6 +1337,9 @@ importers:
|
|||
'@electron-toolkit/preload':
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2(electron@39.1.0)
|
||||
'@moeru/eventa':
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(electron@39.1.0)(h3@2.0.1-rc.5(crossws@0.4.1(srvx@0.9.4)))(web-worker@1.5.0)
|
||||
'@nekopaw/tempora':
|
||||
specifier: 0.3.1-alpha.1
|
||||
version: 0.3.1-alpha.1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue