Spaces:
				
			
			
	
			
			
		Runtime error
		
	
	
	
			
			
	
	
	
	
		
		
		Runtime error
		
	format
Browse files- frontend/src/lib/lcmLive.ts +82 -84
- frontend/src/lib/mediaStream.ts +102 -104
- frontend/src/lib/store.ts +12 -13
- frontend/src/lib/types.ts +28 -29
- frontend/src/lib/utils.ts +42 -36
- frontend/src/routes/+page.ts +1 -1
- frontend/vite.config.ts +2 -2
    	
        frontend/src/lib/lcmLive.ts
    CHANGED
    
    | @@ -1,12 +1,11 @@ | |
| 1 | 
             
            import { writable } from 'svelte/store';
         | 
| 2 |  | 
| 3 | 
            -
             | 
| 4 | 
             
            export enum LCMLiveStatus {
         | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
             
            }
         | 
| 11 |  | 
| 12 | 
             
            const initStatus: LCMLiveStatus = LCMLiveStatus.DISCONNECTED;
         | 
| @@ -16,84 +15,83 @@ export const streamId = writable<string | null>(null); | |
| 16 |  | 
| 17 | 
             
            let websocket: WebSocket | null = null;
         | 
| 18 | 
             
            export const lcmLiveActions = {
         | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
                            websocket = new WebSocket(websocketURL);
         | 
| 28 | 
            -
                            websocket.onopen = () => {
         | 
| 29 | 
            -
                                console.log("Connected to websocket");
         | 
| 30 | 
            -
                            };
         | 
| 31 | 
            -
                            websocket.onclose = () => {
         | 
| 32 | 
            -
                                lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
         | 
| 33 | 
            -
                                console.log("Disconnected from websocket");
         | 
| 34 | 
            -
                            };
         | 
| 35 | 
            -
                            websocket.onerror = (err) => {
         | 
| 36 | 
            -
                                console.error(err);
         | 
| 37 | 
            -
                            };
         | 
| 38 | 
            -
                            websocket.onmessage = (event) => {
         | 
| 39 | 
            -
                                const data = JSON.parse(event.data);
         | 
| 40 | 
            -
                                switch (data.status) {
         | 
| 41 | 
            -
                                    case "connected":
         | 
| 42 | 
            -
                                        lcmLiveStatus.set(LCMLiveStatus.CONNECTED);
         | 
| 43 | 
            -
                                        streamId.set(userId);
         | 
| 44 | 
            -
                                        resolve({ status: "connected", userId });
         | 
| 45 | 
            -
                                        break;
         | 
| 46 | 
            -
                                    case "send_frame":
         | 
| 47 | 
            -
                                        lcmLiveStatus.set(LCMLiveStatus.SEND_FRAME);
         | 
| 48 | 
            -
                                        const streamData = getSreamdata();
         | 
| 49 | 
            -
                                        websocket?.send(JSON.stringify({ status: "next_frame" }));
         | 
| 50 | 
            -
                                        for (const d of streamData) {
         | 
| 51 | 
            -
                                            this.send(d);
         | 
| 52 | 
            -
                                        }
         | 
| 53 | 
            -
                                        break;
         | 
| 54 | 
            -
                                    case "wait":
         | 
| 55 | 
            -
                                        lcmLiveStatus.set(LCMLiveStatus.WAIT);
         | 
| 56 | 
            -
                                        break;
         | 
| 57 | 
            -
                                    case "timeout":
         | 
| 58 | 
            -
                                        console.log("timeout");
         | 
| 59 | 
            -
                                        lcmLiveStatus.set(LCMLiveStatus.TIMEOUT);
         | 
| 60 | 
            -
                                        streamId.set(null);
         | 
| 61 | 
            -
                                        reject(new Error("timeout"));
         | 
| 62 | 
            -
                                        break;
         | 
| 63 | 
            -
                                    case "error":
         | 
| 64 | 
            -
                                        console.log(data.message);
         | 
| 65 | 
            -
                                        lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
         | 
| 66 | 
            -
                                        streamId.set(null);
         | 
| 67 | 
            -
                                        reject(new Error(data.message));
         | 
| 68 | 
            -
                                        break;
         | 
| 69 | 
            -
                                }
         | 
| 70 | 
            -
                            };
         | 
| 71 |  | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
                         | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 92 | 
             
                    lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
         | 
| 93 | 
            -
                    if (websocket) {
         | 
| 94 | 
            -
                        websocket.close();
         | 
| 95 | 
            -
                    }
         | 
| 96 | 
            -
                    websocket = null;
         | 
| 97 | 
             
                    streamId.set(null);
         | 
| 98 | 
            -
             | 
| 99 | 
            -
            } | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
             
            import { writable } from 'svelte/store';
         | 
| 2 |  | 
|  | |
| 3 | 
             
            export enum LCMLiveStatus {
         | 
| 4 | 
            +
              CONNECTED = 'connected',
         | 
| 5 | 
            +
              DISCONNECTED = 'disconnected',
         | 
| 6 | 
            +
              WAIT = 'wait',
         | 
| 7 | 
            +
              SEND_FRAME = 'send_frame',
         | 
| 8 | 
            +
              TIMEOUT = 'timeout'
         | 
| 9 | 
             
            }
         | 
| 10 |  | 
| 11 | 
             
            const initStatus: LCMLiveStatus = LCMLiveStatus.DISCONNECTED;
         | 
|  | |
| 15 |  | 
| 16 | 
             
            let websocket: WebSocket | null = null;
         | 
| 17 | 
             
            export const lcmLiveActions = {
         | 
| 18 | 
            +
              async start(getSreamdata: () => any[]) {
         | 
| 19 | 
            +
                return new Promise((resolve, reject) => {
         | 
| 20 | 
            +
                  try {
         | 
| 21 | 
            +
                    const userId = crypto.randomUUID();
         | 
| 22 | 
            +
                    const websocketURL = `${
         | 
| 23 | 
            +
                      window.location.protocol === 'https:' ? 'wss' : 'ws'
         | 
| 24 | 
            +
                    }:${window.location.host}/api/ws/${userId}`;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 25 |  | 
| 26 | 
            +
                    websocket = new WebSocket(websocketURL);
         | 
| 27 | 
            +
                    websocket.onopen = () => {
         | 
| 28 | 
            +
                      console.log('Connected to websocket');
         | 
| 29 | 
            +
                    };
         | 
| 30 | 
            +
                    websocket.onclose = () => {
         | 
| 31 | 
            +
                      lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
         | 
| 32 | 
            +
                      console.log('Disconnected from websocket');
         | 
| 33 | 
            +
                    };
         | 
| 34 | 
            +
                    websocket.onerror = (err) => {
         | 
| 35 | 
            +
                      console.error(err);
         | 
| 36 | 
            +
                    };
         | 
| 37 | 
            +
                    websocket.onmessage = (event) => {
         | 
| 38 | 
            +
                      const data = JSON.parse(event.data);
         | 
| 39 | 
            +
                      switch (data.status) {
         | 
| 40 | 
            +
                        case 'connected':
         | 
| 41 | 
            +
                          lcmLiveStatus.set(LCMLiveStatus.CONNECTED);
         | 
| 42 | 
            +
                          streamId.set(userId);
         | 
| 43 | 
            +
                          resolve({ status: 'connected', userId });
         | 
| 44 | 
            +
                          break;
         | 
| 45 | 
            +
                        case 'send_frame':
         | 
| 46 | 
            +
                          lcmLiveStatus.set(LCMLiveStatus.SEND_FRAME);
         | 
| 47 | 
            +
                          const streamData = getSreamdata();
         | 
| 48 | 
            +
                          websocket?.send(JSON.stringify({ status: 'next_frame' }));
         | 
| 49 | 
            +
                          for (const d of streamData) {
         | 
| 50 | 
            +
                            this.send(d);
         | 
| 51 | 
            +
                          }
         | 
| 52 | 
            +
                          break;
         | 
| 53 | 
            +
                        case 'wait':
         | 
| 54 | 
            +
                          lcmLiveStatus.set(LCMLiveStatus.WAIT);
         | 
| 55 | 
            +
                          break;
         | 
| 56 | 
            +
                        case 'timeout':
         | 
| 57 | 
            +
                          console.log('timeout');
         | 
| 58 | 
            +
                          lcmLiveStatus.set(LCMLiveStatus.TIMEOUT);
         | 
| 59 | 
            +
                          streamId.set(null);
         | 
| 60 | 
            +
                          reject(new Error('timeout'));
         | 
| 61 | 
            +
                          break;
         | 
| 62 | 
            +
                        case 'error':
         | 
| 63 | 
            +
                          console.log(data.message);
         | 
| 64 | 
            +
                          lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
         | 
| 65 | 
            +
                          streamId.set(null);
         | 
| 66 | 
            +
                          reject(new Error(data.message));
         | 
| 67 | 
            +
                          break;
         | 
| 68 | 
            +
                      }
         | 
| 69 | 
            +
                    };
         | 
| 70 | 
            +
                  } catch (err) {
         | 
| 71 | 
            +
                    console.error(err);
         | 
| 72 | 
             
                    lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
         | 
|  | |
|  | |
|  | |
|  | |
| 73 | 
             
                    streamId.set(null);
         | 
| 74 | 
            +
                    reject(err);
         | 
| 75 | 
            +
                  }
         | 
| 76 | 
            +
                });
         | 
| 77 | 
            +
              },
         | 
| 78 | 
            +
              send(data: Blob | { [key: string]: any }) {
         | 
| 79 | 
            +
                if (websocket && websocket.readyState === WebSocket.OPEN) {
         | 
| 80 | 
            +
                  if (data instanceof Blob) {
         | 
| 81 | 
            +
                    websocket.send(data);
         | 
| 82 | 
            +
                  } else {
         | 
| 83 | 
            +
                    websocket.send(JSON.stringify(data));
         | 
| 84 | 
            +
                  }
         | 
| 85 | 
            +
                } else {
         | 
| 86 | 
            +
                  console.log('WebSocket not connected');
         | 
| 87 | 
            +
                }
         | 
| 88 | 
            +
              },
         | 
| 89 | 
            +
              async stop() {
         | 
| 90 | 
            +
                lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
         | 
| 91 | 
            +
                if (websocket) {
         | 
| 92 | 
            +
                  websocket.close();
         | 
| 93 | 
            +
                }
         | 
| 94 | 
            +
                websocket = null;
         | 
| 95 | 
            +
                streamId.set(null);
         | 
| 96 | 
            +
              }
         | 
| 97 | 
            +
            };
         | 
    	
        frontend/src/lib/mediaStream.ts
    CHANGED
    
    | @@ -1,10 +1,10 @@ | |
| 1 | 
            -
            import { writable, type Writable | 
| 2 |  | 
| 3 | 
             
            const BASE_HEIGHT = 720;
         | 
| 4 | 
             
            export enum MediaStreamStatusEnum {
         | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
             
            }
         | 
| 9 | 
             
            export const onFrameChangeStore: Writable<{ blob: Blob }> = writable({ blob: new Blob() });
         | 
| 10 |  | 
| @@ -13,108 +13,106 @@ export const mediaStreamStatus = writable(MediaStreamStatusEnum.INIT); | |
| 13 | 
             
            export const mediaStream = writable<MediaStream | null>(null);
         | 
| 14 |  | 
| 15 | 
             
            export const mediaStreamActions = {
         | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
|  | |
| 40 |  | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
                 | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
                    };
         | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
                    let captureStream = null;
         | 
| 65 |  | 
| 66 | 
            -
             | 
| 67 | 
            -
                        captureStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
         | 
| 68 | 
            -
                        const videoTrack = captureStream.getVideoTracks()[0];
         | 
| 69 |  | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
                        console.log(JSON.stringify(videoTrack.getConstraints(), null, 2));
         | 
| 74 | 
            -
                        mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
         | 
| 75 | 
            -
                        mediaStream.set(captureStream)
         | 
| 76 |  | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 |  | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
                    } | 
| 102 | 
            -
                     | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
                    });
         | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
            } | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            import { get, writable, type Writable } from 'svelte/store';
         | 
| 2 |  | 
| 3 | 
             
            const BASE_HEIGHT = 720;
         | 
| 4 | 
             
            export enum MediaStreamStatusEnum {
         | 
| 5 | 
            +
              INIT = 'init',
         | 
| 6 | 
            +
              CONNECTED = 'connected',
         | 
| 7 | 
            +
              DISCONNECTED = 'disconnected'
         | 
| 8 | 
             
            }
         | 
| 9 | 
             
            export const onFrameChangeStore: Writable<{ blob: Blob }> = writable({ blob: new Blob() });
         | 
| 10 |  | 
|  | |
| 13 | 
             
            export const mediaStream = writable<MediaStream | null>(null);
         | 
| 14 |  | 
| 15 | 
             
            export const mediaStreamActions = {
         | 
| 16 | 
            +
              async enumerateDevices() {
         | 
| 17 | 
            +
                // console.log("Enumerating devices");
         | 
| 18 | 
            +
                await navigator.mediaDevices
         | 
| 19 | 
            +
                  .enumerateDevices()
         | 
| 20 | 
            +
                  .then((devices) => {
         | 
| 21 | 
            +
                    const cameras = devices.filter((device) => device.kind === 'videoinput');
         | 
| 22 | 
            +
                    mediaDevices.set(cameras);
         | 
| 23 | 
            +
                  })
         | 
| 24 | 
            +
                  .catch((err) => {
         | 
| 25 | 
            +
                    console.error(err);
         | 
| 26 | 
            +
                  });
         | 
| 27 | 
            +
              },
         | 
| 28 | 
            +
              async start(mediaDevicedID?: string, aspectRatio: number = 1) {
         | 
| 29 | 
            +
                const constraints = {
         | 
| 30 | 
            +
                  audio: false,
         | 
| 31 | 
            +
                  video: {
         | 
| 32 | 
            +
                    width: {
         | 
| 33 | 
            +
                      ideal: BASE_HEIGHT * aspectRatio
         | 
| 34 | 
            +
                    },
         | 
| 35 | 
            +
                    height: {
         | 
| 36 | 
            +
                      ideal: BASE_HEIGHT
         | 
| 37 | 
            +
                    },
         | 
| 38 | 
            +
                    deviceId: mediaDevicedID
         | 
| 39 | 
            +
                  }
         | 
| 40 | 
            +
                };
         | 
| 41 |  | 
| 42 | 
            +
                await navigator.mediaDevices
         | 
| 43 | 
            +
                  .getUserMedia(constraints)
         | 
| 44 | 
            +
                  .then((stream) => {
         | 
| 45 | 
            +
                    mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
         | 
| 46 | 
            +
                    mediaStream.set(stream);
         | 
| 47 | 
            +
                  })
         | 
| 48 | 
            +
                  .catch((err) => {
         | 
| 49 | 
            +
                    console.error(`${err.name}: ${err.message}`);
         | 
| 50 | 
            +
                    mediaStreamStatus.set(MediaStreamStatusEnum.DISCONNECTED);
         | 
| 51 | 
            +
                    mediaStream.set(null);
         | 
| 52 | 
            +
                  });
         | 
| 53 | 
            +
              },
         | 
| 54 | 
            +
              async startScreenCapture() {
         | 
| 55 | 
            +
                const displayMediaOptions = {
         | 
| 56 | 
            +
                  video: {
         | 
| 57 | 
            +
                    displaySurface: 'window'
         | 
| 58 | 
            +
                  },
         | 
| 59 | 
            +
                  audio: false,
         | 
| 60 | 
            +
                  surfaceSwitching: 'include'
         | 
| 61 | 
            +
                };
         | 
|  | |
|  | |
|  | |
|  | |
| 62 |  | 
| 63 | 
            +
                let captureStream = null;
         | 
|  | |
|  | |
| 64 |  | 
| 65 | 
            +
                try {
         | 
| 66 | 
            +
                  captureStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
         | 
| 67 | 
            +
                  const videoTrack = captureStream.getVideoTracks()[0];
         | 
|  | |
|  | |
|  | |
| 68 |  | 
| 69 | 
            +
                  console.log('Track settings:');
         | 
| 70 | 
            +
                  console.log(JSON.stringify(videoTrack.getSettings(), null, 2));
         | 
| 71 | 
            +
                  console.log('Track constraints:');
         | 
| 72 | 
            +
                  console.log(JSON.stringify(videoTrack.getConstraints(), null, 2));
         | 
| 73 | 
            +
                  mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
         | 
| 74 | 
            +
                  mediaStream.set(captureStream);
         | 
| 75 |  | 
| 76 | 
            +
                  const capabilities = videoTrack.getCapabilities();
         | 
| 77 | 
            +
                  const aspectRatio = capabilities.aspectRatio;
         | 
| 78 | 
            +
                  console.log('Aspect Ratio Constraints:', aspectRatio);
         | 
| 79 | 
            +
                } catch (err) {
         | 
| 80 | 
            +
                  console.error(err);
         | 
| 81 | 
            +
                }
         | 
| 82 | 
            +
              },
         | 
| 83 | 
            +
              async switchCamera(mediaDevicedID: string, aspectRatio: number) {
         | 
| 84 | 
            +
                console.log('Switching camera');
         | 
| 85 | 
            +
                if (get(mediaStreamStatus) !== MediaStreamStatusEnum.CONNECTED) {
         | 
| 86 | 
            +
                  return;
         | 
| 87 | 
            +
                }
         | 
| 88 | 
            +
                const constraints = {
         | 
| 89 | 
            +
                  audio: false,
         | 
| 90 | 
            +
                  video: {
         | 
| 91 | 
            +
                    width: {
         | 
| 92 | 
            +
                      ideal: BASE_HEIGHT * aspectRatio
         | 
| 93 | 
            +
                    },
         | 
| 94 | 
            +
                    height: {
         | 
| 95 | 
            +
                      ideal: BASE_HEIGHT
         | 
| 96 | 
            +
                    },
         | 
| 97 | 
            +
                    deviceId: mediaDevicedID
         | 
| 98 | 
            +
                  }
         | 
| 99 | 
            +
                };
         | 
| 100 | 
            +
                console.log('Switching camera', constraints);
         | 
| 101 | 
            +
                await navigator.mediaDevices
         | 
| 102 | 
            +
                  .getUserMedia(constraints)
         | 
| 103 | 
            +
                  .then((stream) => {
         | 
| 104 | 
            +
                    mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
         | 
| 105 | 
            +
                    mediaStream.set(stream);
         | 
| 106 | 
            +
                  })
         | 
| 107 | 
            +
                  .catch((err) => {
         | 
| 108 | 
            +
                    console.error(`${err.name}: ${err.message}`);
         | 
| 109 | 
            +
                  });
         | 
| 110 | 
            +
              },
         | 
| 111 | 
            +
              async stop() {
         | 
| 112 | 
            +
                navigator.mediaDevices.getUserMedia({ video: true }).then((stream) => {
         | 
| 113 | 
            +
                  stream.getTracks().forEach((track) => track.stop());
         | 
| 114 | 
            +
                });
         | 
| 115 | 
            +
                mediaStreamStatus.set(MediaStreamStatusEnum.DISCONNECTED);
         | 
| 116 | 
            +
                mediaStream.set(null);
         | 
| 117 | 
            +
              }
         | 
| 118 | 
            +
            };
         | 
    	
        frontend/src/lib/store.ts
    CHANGED
    
    | @@ -1,15 +1,14 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            import { derived, writable, get, type Writable, type Readable } from 'svelte/store';
         | 
| 3 |  | 
| 4 | 
             
            export const pipelineValues: Writable<Record<string, any>> = writable({});
         | 
| 5 | 
            -
            export const deboucedPipelineValues: Readable<Record<string, any>>
         | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
                 | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
            export const getPipelineValues = () => get(pipelineValues);
         | 
|  | |
| 1 | 
            +
            import { derived, get, writable, type Readable, type Writable } from 'svelte/store';
         | 
|  | |
| 2 |  | 
| 3 | 
             
            export const pipelineValues: Writable<Record<string, any>> = writable({});
         | 
| 4 | 
            +
            export const deboucedPipelineValues: Readable<Record<string, any>> = derived(
         | 
| 5 | 
            +
              pipelineValues,
         | 
| 6 | 
            +
              ($pipelineValues, set) => {
         | 
| 7 | 
            +
                const debounced = setTimeout(() => {
         | 
| 8 | 
            +
                  set($pipelineValues);
         | 
| 9 | 
            +
                }, 100);
         | 
| 10 | 
            +
                return () => clearTimeout(debounced);
         | 
| 11 | 
            +
              }
         | 
| 12 | 
            +
            );
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            export const getPipelineValues = () => get(pipelineValues);
         | 
    	
        frontend/src/lib/types.ts
    CHANGED
    
    | @@ -1,40 +1,39 @@ | |
| 1 | 
             
            export const enum FieldType {
         | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
             
            }
         | 
| 8 | 
             
            export const enum PipelineMode {
         | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
             
            }
         | 
| 13 |  | 
| 14 | 
            -
             | 
| 15 | 
             
            export interface Fields {
         | 
| 16 | 
            -
             | 
| 17 | 
             
            }
         | 
| 18 |  | 
| 19 | 
             
            export interface FieldProps {
         | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
             
            }
         | 
| 31 | 
             
            export interface PipelineInfo {
         | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
            }
         | 
|  | |
| 1 | 
             
            export const enum FieldType {
         | 
| 2 | 
            +
              RANGE = 'range',
         | 
| 3 | 
            +
              SEED = 'seed',
         | 
| 4 | 
            +
              TEXTAREA = 'textarea',
         | 
| 5 | 
            +
              CHECKBOX = 'checkbox',
         | 
| 6 | 
            +
              SELECT = 'select'
         | 
| 7 | 
             
            }
         | 
| 8 | 
             
            export const enum PipelineMode {
         | 
| 9 | 
            +
              IMAGE = 'image',
         | 
| 10 | 
            +
              VIDEO = 'video',
         | 
| 11 | 
            +
              TEXT = 'text'
         | 
| 12 | 
             
            }
         | 
| 13 |  | 
|  | |
| 14 | 
             
            export interface Fields {
         | 
| 15 | 
            +
              [key: string]: FieldProps;
         | 
| 16 | 
             
            }
         | 
| 17 |  | 
| 18 | 
             
            export interface FieldProps {
         | 
| 19 | 
            +
              default: number | string;
         | 
| 20 | 
            +
              max?: number;
         | 
| 21 | 
            +
              min?: number;
         | 
| 22 | 
            +
              title: string;
         | 
| 23 | 
            +
              field: FieldType;
         | 
| 24 | 
            +
              step?: number;
         | 
| 25 | 
            +
              disabled?: boolean;
         | 
| 26 | 
            +
              hide?: boolean;
         | 
| 27 | 
            +
              id: string;
         | 
| 28 | 
            +
              values?: string[];
         | 
| 29 | 
             
            }
         | 
| 30 | 
             
            export interface PipelineInfo {
         | 
| 31 | 
            +
              title: {
         | 
| 32 | 
            +
                default: string;
         | 
| 33 | 
            +
              };
         | 
| 34 | 
            +
              name: string;
         | 
| 35 | 
            +
              description: string;
         | 
| 36 | 
            +
              input_mode: {
         | 
| 37 | 
            +
                default: PipelineMode;
         | 
| 38 | 
            +
              };
         | 
| 39 | 
            +
            }
         | 
    	
        frontend/src/lib/utils.ts
    CHANGED
    
    | @@ -1,44 +1,46 @@ | |
| 1 | 
            -
            import * as piexif from  | 
| 2 |  | 
| 3 | 
             
            interface IImageInfo {
         | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
             
            }
         | 
| 9 |  | 
| 10 | 
             
            export function snapImage(imageEl: HTMLImageElement, info: IImageInfo) {
         | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
|  | |
|  | |
| 19 |  | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 |  | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 |  | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
             
            }
         | 
| 39 |  | 
| 40 | 
            -
            export function expandWindow( | 
| 41 | 
            -
             | 
| 42 | 
             
                <html>
         | 
| 43 | 
             
                    <head>
         | 
| 44 | 
             
                        <title>Real-Time Latent Consistency Model</title>
         | 
| @@ -71,11 +73,15 @@ export function expandWindow(steramURL: string): Window { | |
| 71 | 
             
                            }
         | 
| 72 | 
             
                        </script>
         | 
| 73 |  | 
| 74 | 
            -
                        <img src="${ | 
| 75 | 
             
                    </body>
         | 
| 76 | 
             
                </html>
         | 
| 77 | 
             
                `;
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                 | 
| 80 | 
            -
                 | 
| 81 | 
            -
             | 
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            import * as piexif from 'piexifjs';
         | 
| 2 |  | 
| 3 | 
             
            interface IImageInfo {
         | 
| 4 | 
            +
              prompt?: string;
         | 
| 5 | 
            +
              negative_prompt?: string;
         | 
| 6 | 
            +
              seed?: number;
         | 
| 7 | 
            +
              guidance_scale?: number;
         | 
| 8 | 
             
            }
         | 
| 9 |  | 
| 10 | 
             
            export function snapImage(imageEl: HTMLImageElement, info: IImageInfo) {
         | 
| 11 | 
            +
              try {
         | 
| 12 | 
            +
                const zeroth: { [key: string]: any } = {};
         | 
| 13 | 
            +
                const exif: { [key: string]: any } = {};
         | 
| 14 | 
            +
                const gps: { [key: string]: any } = {};
         | 
| 15 | 
            +
                zeroth[piexif.ImageIFD.Make] = 'LCM Image-to-Image ControNet';
         | 
| 16 | 
            +
                zeroth[piexif.ImageIFD.ImageDescription] =
         | 
| 17 | 
            +
                  `prompt: ${info?.prompt} | negative_prompt: ${info?.negative_prompt} | seed: ${info?.seed} | guidance_scale: ${info?.guidance_scale}`;
         | 
| 18 | 
            +
                zeroth[piexif.ImageIFD.Software] =
         | 
| 19 | 
            +
                  'https://github.com/radames/Real-Time-Latent-Consistency-Model';
         | 
| 20 | 
            +
                exif[piexif.ExifIFD.DateTimeOriginal] = new Date().toISOString();
         | 
| 21 |  | 
| 22 | 
            +
                const exifObj = { '0th': zeroth, Exif: exif, GPS: gps };
         | 
| 23 | 
            +
                const exifBytes = piexif.dump(exifObj);
         | 
| 24 |  | 
| 25 | 
            +
                const canvas = document.createElement('canvas');
         | 
| 26 | 
            +
                canvas.width = imageEl.naturalWidth;
         | 
| 27 | 
            +
                canvas.height = imageEl.naturalHeight;
         | 
| 28 | 
            +
                const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
         | 
| 29 | 
            +
                ctx.drawImage(imageEl, 0, 0);
         | 
| 30 | 
            +
                const dataURL = canvas.toDataURL('image/jpeg');
         | 
| 31 | 
            +
                const withExif = piexif.insert(exifBytes, dataURL);
         | 
| 32 |  | 
| 33 | 
            +
                const a = document.createElement('a');
         | 
| 34 | 
            +
                a.href = withExif;
         | 
| 35 | 
            +
                a.download = `lcm_txt_2_img${Date.now()}.png`;
         | 
| 36 | 
            +
                a.click();
         | 
| 37 | 
            +
              } catch (err) {
         | 
| 38 | 
            +
                console.log(err);
         | 
| 39 | 
            +
              }
         | 
| 40 | 
             
            }
         | 
| 41 |  | 
| 42 | 
            +
            export function expandWindow(streamURL: string): Window {
         | 
| 43 | 
            +
              const html = `
         | 
| 44 | 
             
                <html>
         | 
| 45 | 
             
                    <head>
         | 
| 46 | 
             
                        <title>Real-Time Latent Consistency Model</title>
         | 
|  | |
| 73 | 
             
                            }
         | 
| 74 | 
             
                        </script>
         | 
| 75 |  | 
| 76 | 
            +
                        <img src="${streamURL}" style="width: 100%; height: 100%; object-fit: contain;" />
         | 
| 77 | 
             
                    </body>
         | 
| 78 | 
             
                </html>
         | 
| 79 | 
             
                `;
         | 
| 80 | 
            +
              const newWindow = window.open(
         | 
| 81 | 
            +
                '',
         | 
| 82 | 
            +
                '_blank',
         | 
| 83 | 
            +
                'width=1024,height=1024,scrollbars=0,resizable=1,toolbar=0,menubar=0,location=0,directories=0,status=0'
         | 
| 84 | 
            +
              ) as Window;
         | 
| 85 | 
            +
              newWindow.document.write(html);
         | 
| 86 | 
            +
              return newWindow;
         | 
| 87 | 
            +
            }
         | 
    	
        frontend/src/routes/+page.ts
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            export const prerender = true
         | 
|  | |
| 1 | 
            +
            export const prerender = true;
         | 
    	
        frontend/vite.config.ts
    CHANGED
    
    | @@ -10,6 +10,6 @@ export default defineConfig({ | |
| 10 | 
             
                    target: 'ws://localhost:7860',
         | 
| 11 | 
             
                    ws: true
         | 
| 12 | 
             
                  }
         | 
| 13 | 
            -
                } | 
| 14 | 
             
              }
         | 
| 15 | 
            -
            });
         | 
|  | |
| 10 | 
             
                    target: 'ws://localhost:7860',
         | 
| 11 | 
             
                    ws: true
         | 
| 12 | 
             
                  }
         | 
| 13 | 
            +
                }
         | 
| 14 | 
             
              }
         | 
| 15 | 
            +
            });
         | 
