11// preload.js
22const { ipcRenderer, contextBridge } = require ( 'electron' ) ;
33
4+ // Native modules - try direct require first (works when sandbox disabled),
5+ // fall back to IPC-based approach when in sandbox mode.
6+
47let WindowAudioStream = null ;
8+ let ElectronAsio = null ;
9+ let useIpcForWindowAudio = false ;
10+ let useIpcForAsio = false ;
11+
12+ // Try loading WindowAudioStream directly (sandbox disabled mode)
513try {
614 WindowAudioStream = require ( './window-audio-stream.js' ) ;
715 if ( WindowAudioStream && typeof WindowAudioStream !== 'function' && WindowAudioStream . default ) {
816 WindowAudioStream = WindowAudioStream . default ;
917 }
18+ console . log ( '[Electron Capture] WindowAudioStream loaded directly' ) ;
1019} catch ( error ) {
11- console . error ( 'Failed to load WindowAudioStream module:' , error ) ;
20+ // Sandbox mode - use IPC fallback
21+ useIpcForWindowAudio = true ;
22+ console . log ( '[Electron Capture] WindowAudioStream will use IPC (sandbox mode)' ) ;
1223}
1324
14- // ASIO Audio Capture (Windows only - for professional audio interfaces )
15- let ElectronAsio = null ;
16- try {
17- ElectronAsio = require ( './native-modules/electron-asio/index.js' ) ;
18- if ( ElectronAsio && ElectronAsio . initialize ) {
19- ElectronAsio . initialize ( ) ;
20- console . log ( '[Electron Capture] ASIO module loaded:' , ElectronAsio . getVersionInfo ( ) ) ;
21- }
22- } catch ( error ) {
23- // ASIO is optional and Windows-only
24- if ( process . platform === 'win32' ) {
25- console . warn ( ' ASIO module not available:' , error . message ) ;
25+ // Try loading ElectronAsio directly (sandbox disabled mode, Windows only )
26+ if ( process . platform === 'win32' ) {
27+ try {
28+ ElectronAsio = require ( './native-modules/electron-asio/index.js' ) ;
29+ if ( ElectronAsio && ElectronAsio . initialize ) {
30+ ElectronAsio . initialize ( ) ;
31+ console . log ( '[Electron Capture] ASIO module loaded directly :' , ElectronAsio . getVersionInfo ( ) ) ;
32+ }
33+ } catch ( error ) {
34+ // Sandbox mode - use IPC fallback
35+ useIpcForAsio = true ;
36+ console . log ( '[Electron Capture] ASIO will use IPC (sandbox mode)' ) ;
2637 }
2738}
2839
@@ -715,7 +726,19 @@ function createElectronApi() {
715726 return { success : true , target : updated } ;
716727 } ,
717728 'getAppAudioTarget' : ( ) => appAudioTarget ,
718- 'isWindowAudioCaptureAvailable' : ( ) => Boolean ( WindowAudioStream ) ,
729+ 'isWindowAudioCaptureAvailable' : ( ) => {
730+ // Direct mode: check module loaded
731+ if ( ! useIpcForWindowAudio ) return Boolean ( WindowAudioStream ) ;
732+ // IPC mode: assume available if main process loaded it (async check done at startup)
733+ return true ; // Actual availability checked via windowAudio:getTargets
734+ } ,
735+ 'isWindowAudioCaptureAvailableAsync' : async ( ) => {
736+ if ( ! useIpcForWindowAudio ) return Boolean ( WindowAudioStream ) ;
737+ try {
738+ const result = await ipcRenderer . invoke ( 'windowAudio:getTargets' ) ;
739+ return result && result . success !== false ;
740+ } catch { return false ; }
741+ } ,
719742 // Custom Electron capture preferences (v39.2.7+)
720743 'getCapturePreferences' : ( ) => ( { ...capturePreferences } ) ,
721744 'getPlayoutDelay' : ( ) => capturePreferences . playoutDelay ,
@@ -731,16 +754,105 @@ function createElectronApi() {
731754 }
732755 return false ;
733756 } ,
734- // ASIO Audio Capture (Windows only)
735- 'isAsioAvailable' : ( ) => Boolean ( ElectronAsio && ElectronAsio . isAvailable && ElectronAsio . isAvailable ( ) ) ,
736- 'getAsioDevices' : ( ) => ElectronAsio ? ElectronAsio . getDevices ( ) : [ ] ,
737- 'getAsioDeviceInfo' : ( deviceIndex ) => ElectronAsio ? ElectronAsio . getDeviceInfo ( deviceIndex ) : null ,
738- 'getAsioVersionInfo' : ( ) => ElectronAsio ? ElectronAsio . getVersionInfo ( ) : 'ASIO not available' ,
757+ // ASIO Audio Capture (Windows only) - supports both direct and IPC modes
758+ 'isAsioAvailable' : ( ) => {
759+ if ( ! useIpcForAsio ) {
760+ return Boolean ( ElectronAsio && ElectronAsio . isAvailable && ElectronAsio . isAvailable ( ) ) ;
761+ }
762+ // IPC mode: return false synchronously, use async version for accurate check
763+ return false ;
764+ } ,
765+ 'isAsioAvailableAsync' : async ( ) => {
766+ if ( ! useIpcForAsio ) {
767+ return Boolean ( ElectronAsio && ElectronAsio . isAvailable && ElectronAsio . isAvailable ( ) ) ;
768+ }
769+ try {
770+ return await ipcRenderer . invoke ( 'asio:isAvailable' ) ;
771+ } catch { return false ; }
772+ } ,
773+ 'getAsioDevices' : ( ) => {
774+ if ( ! useIpcForAsio ) return ElectronAsio ? ElectronAsio . getDevices ( ) : [ ] ;
775+ console . warn ( 'getAsioDevices: Use getAsioDevicesAsync in sandbox mode' ) ;
776+ return [ ] ;
777+ } ,
778+ 'getAsioDevicesAsync' : async ( ) => {
779+ if ( ! useIpcForAsio ) return ElectronAsio ? ElectronAsio . getDevices ( ) : [ ] ;
780+ try {
781+ return await ipcRenderer . invoke ( 'asio:getDevices' ) ;
782+ } catch { return [ ] ; }
783+ } ,
784+ 'getAsioDeviceInfo' : ( deviceIndex ) => {
785+ if ( ! useIpcForAsio ) return ElectronAsio ? ElectronAsio . getDeviceInfo ( deviceIndex ) : null ;
786+ console . warn ( 'getAsioDeviceInfo: Use getAsioDeviceInfoAsync in sandbox mode' ) ;
787+ return null ;
788+ } ,
789+ 'getAsioDeviceInfoAsync' : async ( deviceIndex ) => {
790+ if ( ! useIpcForAsio ) return ElectronAsio ? ElectronAsio . getDeviceInfo ( deviceIndex ) : null ;
791+ try {
792+ return await ipcRenderer . invoke ( 'asio:getDeviceInfo' , deviceIndex ) ;
793+ } catch { return null ; }
794+ } ,
795+ 'getAsioVersionInfo' : ( ) => {
796+ if ( ! useIpcForAsio ) return ElectronAsio ? ElectronAsio . getVersionInfo ( ) : 'ASIO not available' ;
797+ return 'ASIO (IPC mode)' ;
798+ } ,
799+ 'getAsioVersionInfoAsync' : async ( ) => {
800+ if ( ! useIpcForAsio ) return ElectronAsio ? ElectronAsio . getVersionInfo ( ) : 'ASIO not available' ;
801+ try {
802+ return await ipcRenderer . invoke ( 'asio:getVersionInfo' ) ;
803+ } catch { return 'ASIO not available' ; }
804+ } ,
739805 'createAsioStream' : ( options ) => {
740- if ( ! ElectronAsio || ! ElectronAsio . AsioStream ) {
741- throw new Error ( 'ASIO module not available' ) ;
806+ if ( ! useIpcForAsio ) {
807+ if ( ! ElectronAsio || ! ElectronAsio . AsioStream ) {
808+ throw new Error ( 'ASIO module not available' ) ;
809+ }
810+ return new ElectronAsio . AsioStream ( options ) ;
742811 }
743- return new ElectronAsio . AsioStream ( options ) ;
812+ throw new Error ( 'createAsioStream: Use createAsioStreamAsync in sandbox mode' ) ;
813+ } ,
814+ 'createAsioStreamAsync' : async ( options ) => {
815+ if ( ! useIpcForAsio ) {
816+ if ( ! ElectronAsio || ! ElectronAsio . AsioStream ) {
817+ throw new Error ( 'ASIO module not available' ) ;
818+ }
819+ return new ElectronAsio . AsioStream ( options ) ;
820+ }
821+ // IPC mode: create stream in main process, return control object
822+ try {
823+ const streamInfo = await ipcRenderer . invoke ( 'asio:createStream' , options ) ;
824+ return {
825+ streamId : streamInfo . streamId ,
826+ inputLatency : streamInfo . inputLatency ,
827+ outputLatency : streamInfo . outputLatency ,
828+ sampleRate : streamInfo . sampleRate ,
829+ bufferSize : streamInfo . bufferSize ,
830+ start : ( ) => ipcRenderer . invoke ( 'asio:startStream' , streamInfo . streamId ) ,
831+ stop : ( ) => ipcRenderer . invoke ( 'asio:stopStream' , streamInfo . streamId ) ,
832+ close : ( ) => ipcRenderer . invoke ( 'asio:closeStream' , streamInfo . streamId ) ,
833+ getStats : ( ) => ipcRenderer . invoke ( 'asio:getStreamStats' , streamInfo . streamId ) ,
834+ write : ( buffers ) => {
835+ const serialized = buffers . map ( buf => Array . from ( buf ) ) ;
836+ return ipcRenderer . invoke ( 'asio:writeStream' , streamInfo . streamId , serialized ) ;
837+ }
838+ } ;
839+ } catch ( err ) {
840+ throw new Error ( 'Failed to create ASIO stream: ' + err . message ) ;
841+ }
842+ } ,
843+ // Subscribe to ASIO audio data (IPC mode only)
844+ 'onAsioAudioData' : ( callback ) => {
845+ const handler = ( event , { streamId, buffers } ) => {
846+ const float32Buffers = buffers . map ( arr => new Float32Array ( arr ) ) ;
847+ callback ( streamId , float32Buffers ) ;
848+ } ;
849+ ipcRenderer . on ( 'asio:audioData' , handler ) ;
850+ return ( ) => ipcRenderer . removeListener ( 'asio:audioData' , handler ) ;
851+ } ,
852+ 'onAsioError' : ( callback ) => {
853+ const handler = ( event , { streamId, error } ) => callback ( streamId , error ) ;
854+ ipcRenderer . on ( 'asio:error' , handler ) ;
855+ return ( ) => ipcRenderer . removeListener ( 'asio:error' , handler ) ;
744856 }
745857 } ;
746858}
0 commit comments