diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0ed87b8..b8fcece 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -17,6 +17,7 @@ "@tiptap/pm": "^3.20.4", "@tiptap/starter-kit": "^3.20.4", "d3": "^7.9.0", + "livekit-client": "^2.17.3", "spacetimedb": "^2.0.4", "wavesurfer.js": "^7.12.4" }, @@ -96,6 +97,12 @@ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, + "node_modules/@bufbuild/protobuf": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz", + "integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", @@ -557,6 +564,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@livekit/mutex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@livekit/mutex/-/mutex-1.1.1.tgz", + "integrity": "sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==", + "license": "Apache-2.0" + }, + "node_modules/@livekit/protocol": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.44.0.tgz", + "integrity": "sha512-/vfhDUGcUKO8Q43r6i+5FrDhl5oZjm/X3U4x2Iciqvgn5C8qbj+57YPcWSJ1kyIZm5Cm6AV2nAPjMm3ETD/iyg==", + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/protobuf": "^1.10.0" + } + }, "node_modules/@panva/hkdf": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", @@ -2079,6 +2101,13 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/dom-mediacapture-record": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@types/dom-mediacapture-record/-/dom-mediacapture-record-1.0.22.tgz", + "integrity": "sha512-mUMZLK3NvwRLcAAT9qmcK+9p7tpU2FHdDsntR3YI4+GY88XrgG4XiE7u1Q2LAN2/FZOz/tdMDC3GQCR4T8nFuw==", + "license": "MIT", + "peer": true + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2789,6 +2818,15 @@ "dev": true, "license": "MIT" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -3202,12 +3240,45 @@ "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", "license": "MIT" }, + "node_modules/livekit-client": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.17.3.tgz", + "integrity": "sha512-htwsAL/BMylY/zwdcT/z00U789csbi9DldSW7DO+5tz7Q15pwu++E1X+ZdtZDfkmlysfQLLibdcqlyg9FY7veQ==", + "license": "Apache-2.0", + "dependencies": { + "@livekit/mutex": "1.1.1", + "@livekit/protocol": "1.44.0", + "events": "^3.3.0", + "jose": "^6.1.0", + "loglevel": "^1.9.2", + "sdp-transform": "^2.15.0", + "tslib": "2.8.1", + "typed-emitter": "^2.1.0", + "webrtc-adapter": "^9.0.1" + }, + "peerDependencies": { + "@types/dom-mediacapture-record": "^1" + } + }, "node_modules/locate-character": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", "license": "MIT" }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -3718,6 +3789,16 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", "license": "BSD-3-Clause" }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -3746,6 +3827,21 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sdp": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.1.tgz", + "integrity": "sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==", + "license": "MIT" + }, + "node_modules/sdp-transform": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz", + "integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==", + "license": "MIT", + "bin": { + "sdp-verify": "checker.js" + } + }, "node_modules/set-cookie-parser": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz", @@ -3938,6 +4034,21 @@ "node": ">=6" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", + "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", + "license": "MIT", + "optionalDependencies": { + "rxjs": "*" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -4069,6 +4180,19 @@ "integrity": "sha512-b/+XnWfJejNdvNUmtm4M5QzQepHhUbTo+62wYybwdV1B/Sn9vHhgb1xckRm0rGY2ZefJwLkE7lYcKnLfIia4cQ==", "license": "BSD-3-Clause" }, + "node_modules/webrtc-adapter": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.4.tgz", + "integrity": "sha512-5ZZY1+lGq8LEKuDlg9M2RPJHlH3R7OVwyHqMcUsLKCgd9Wvf+QrFTCItkXXYPmrJn8H6gRLXbSgxLLdexiqHxw==", + "license": "BSD-3-Clause", + "dependencies": { + "sdp": "^3.2.0" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">=3.10.0" + } + }, "node_modules/zimmerframe": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index f3f8b33..3c11bcc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,6 +33,7 @@ "@tiptap/pm": "^3.20.4", "@tiptap/starter-kit": "^3.20.4", "d3": "^7.9.0", + "livekit-client": "^2.17.3", "spacetimedb": "^2.0.4", "wavesurfer.js": "^7.12.4" } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index c8c9bf7..9ffb80f 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1148,6 +1148,51 @@ export async function fetchMyUsage( return res.json(); } +// ============================================================================= +// LiveKit / Kommunikasjonsrom (oppgave 16.1) +// ============================================================================= + +export interface JoinCommunicationRequest { + communication_id: string; + role?: 'publisher' | 'subscriber'; +} + +export interface RoomParticipantInfo { + user_id: string; + display_name: string; + role: string; +} + +export interface JoinCommunicationResponse { + livekit_room_name: string; + livekit_token: string; + livekit_url: string; + identity: string; + participants: RoomParticipantInfo[]; +} + +export interface LeaveCommunicationResponse { + status: string; +} + +/** Bli med i et LiveKit-rom for en kommunikasjonsnode. */ +export function joinCommunication( + accessToken: string, + data: JoinCommunicationRequest +): Promise { + return post(accessToken, '/intentions/join_communication', data); +} + +/** Forlat et LiveKit-rom. */ +export function leaveCommunication( + accessToken: string, + communicationId: string +): Promise { + return post(accessToken, '/intentions/leave_communication', { + communication_id: communicationId + }); +} + /** Hent ressursforbruk for en spesifikk node (kun eier). */ export async function fetchNodeUsage( accessToken: string, diff --git a/frontend/src/lib/components/traits/RecordingTrait.svelte b/frontend/src/lib/components/traits/RecordingTrait.svelte index ab9bb91..aedc163 100644 --- a/frontend/src/lib/components/traits/RecordingTrait.svelte +++ b/frontend/src/lib/components/traits/RecordingTrait.svelte @@ -1,17 +1,216 @@ {#snippet children()} -

LiveKit-studio for opptak og sanntidslyd.

+ {#if !accessToken} +

Logg inn for Γ₯ bruke opptak.

+ {:else if !primaryComm} +

Ingen kommunikasjonskanal knyttet til denne samlingen.

+ {:else} + +
+
+ + + {#if isConnected} + Tilkoblet + {:else if isConnecting} + Kobler til… + {:else} + Frakoblet + {/if} + +
+ +
+ {#if isConnected} + + + {:else} + + {/if} +
+
+ + + {#if error} +
+ {error} +
+ {/if} + + + {#if isConnected && participants.length > 0} +
+

+ Deltakere ({participants.length}) +

+
    + {#each participants as p (p.identity)} +
  • + + + + + + {p.displayName} + {#if p.identity === localIdentity} + (deg) + {/if} + + + + {#if p.isMuted} + + + + + + {/if} +
  • + {/each} +
+
+ {/if} + + + {#if isConnected && joinInfo} +

+ Rom: {joinInfo.livekit_room_name} +

+ {/if} + {/if} {/snippet}
diff --git a/frontend/src/lib/livekit.ts b/frontend/src/lib/livekit.ts new file mode 100644 index 0000000..0f5bba1 --- /dev/null +++ b/frontend/src/lib/livekit.ts @@ -0,0 +1,274 @@ +/** + * LiveKit client wrapper for Synops. + * + * Handles room connection, participant tracking, and Web Audio routing. + * LiveKit's auto-attach of