From cb7839293a4e68ea603616af7d6bdcf0e22b823e Mon Sep 17 00:00:00 2001 From: Benjamin Wallberg Date: Mon, 26 Aug 2024 15:40:56 +0200 Subject: [PATCH] WIP! feat: add media & html websocket connections --- .env.sample | 3 ++ package-lock.json | 32 ++++++++++++++- package.json | 6 ++- src/api/agileLive/websocket.ts | 47 +++++++++++++++++++++ src/app/html_input/page.tsx | 10 +++++ src/app/production/[id]/page.tsx | 12 ++++-- src/components/addInput/AddInput.tsx | 57 ++++++++++++++++++++++++++ src/components/addSource/AddSource.tsx | 24 ----------- src/hooks/productions.ts | 3 +- src/interfaces/production.ts | 14 +++++++ src/middleware.ts | 2 +- 11 files changed, 178 insertions(+), 32 deletions(-) create mode 100644 src/api/agileLive/websocket.ts create mode 100644 src/app/html_input/page.tsx create mode 100644 src/components/addInput/AddInput.tsx delete mode 100644 src/components/addSource/AddSource.tsx diff --git a/.env.sample b/.env.sample index 8f85e45f..a0cf9115 100644 --- a/.env.sample +++ b/.env.sample @@ -14,3 +14,6 @@ BCRYPT_SALT_ROUNDS=${BCRYPT_SALT_ROUNDS:-10} # i18n UI_LANG=${UI_LANG:-en} + +# Mediaplayer - path on the system controller +MEDIAPLAYER_PLACEHOLDER=/media/media_placeholder.mp4 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7078d75e..67a1dde1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@sinclair/typebox": "^0.25.24", "@tabler/icons": "^2.22.0", "@tabler/icons-react": "^2.20.0", + "@types/ws": "^8.5.12", "bcrypt": "^5.1.0", "cron": "^2.3.1", "date-fns": "^2.30.0", @@ -35,7 +36,8 @@ "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", "tailwind-merge": "^1.13.2", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "ws": "^8.18.0" }, "devDependencies": { "@commitlint/cli": "^17.4.2", @@ -2512,6 +2514,14 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -11216,6 +11226,26 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index f3a8e128..212bbeec 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "pretty:format": "prettier --write .", "typecheck": "tsc --noEmit -p tsconfig.json", "lint": "next lint", - "dev": "./update_gui_version.sh && next dev", + "dev": "next dev", "build": "next build", "start": "next start", "version:rc": "npm version prerelease --preid=rc", @@ -32,6 +32,7 @@ "@sinclair/typebox": "^0.25.24", "@tabler/icons": "^2.22.0", "@tabler/icons-react": "^2.20.0", + "@types/ws": "^8.5.12", "bcrypt": "^5.1.0", "cron": "^2.3.1", "date-fns": "^2.30.0", @@ -48,7 +49,8 @@ "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", "tailwind-merge": "^1.13.2", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "ws": "^8.18.0" }, "devDependencies": { "@commitlint/cli": "^17.4.2", diff --git a/src/api/agileLive/websocket.ts b/src/api/agileLive/websocket.ts new file mode 100644 index 00000000..57c50697 --- /dev/null +++ b/src/api/agileLive/websocket.ts @@ -0,0 +1,47 @@ +import WebSocket from 'ws'; + +function createWebSocket(): Promise { + return new Promise((resolve, reject) => { + const ws = new WebSocket(`ws://${process.env.AGILE_WEBSOCKET}`); + ws.on('error', reject); + ws.on('open', () => { + // const send = ws.send.bind(ws); + // ws.send = (message) => { + // console.debug(`[websocket] sending message: ${message}`); + // send(message); + // }; + resolve(ws); + }); + }); +} + +export async function createHtmlWebSocket() { + const ws = await createWebSocket(); + return { + create: (input: number) => { + ws.send(`html create ${input} 1920 1080`); + // delay the loading of the placeholder page to ensure the browser instance is ready + setTimeout(() => { + ws.send( + `html load ${input} ${process.env.NEXTAUTH_URL}/html_input?input=${input}` + ); + }, 1000); + }, + close: (input: number) => { + ws.send(`html close ${input}`); + } + }; +} + +export async function createMediaplayerWebSocket() { + const ws = await createWebSocket(); + return { + create: (input: number) => { + ws.send(`media create ${input} ${process.env.MEDIAPLAYER_PLACEHOLDER}`); + ws.send(`media play ${input}`); + }, + close: (input: number) => { + ws.send(`media close ${input}`); + } + }; +} diff --git a/src/app/html_input/page.tsx b/src/app/html_input/page.tsx new file mode 100644 index 00000000..81cfaa52 --- /dev/null +++ b/src/app/html_input/page.tsx @@ -0,0 +1,10 @@ +import { PageProps } from '../../../.next/types/app/html_input/page'; + +export default function HtmlInput({ searchParams: { input } }: PageProps) { + return ( +
+

HTML INPUT

+

{input}

+
+ ); +} diff --git a/src/app/production/[id]/page.tsx b/src/app/production/[id]/page.tsx index 8faac496..d68bd179 100644 --- a/src/app/production/[id]/page.tsx +++ b/src/app/production/[id]/page.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState, KeyboardEvent } from 'react'; import { PageProps } from '../../../../.next/types/app/production/[id]/page'; import SourceListItem from '../../../components/sourceListItem/SourceListItem'; import FilterOptions from '../../../components/filter/FilterOptions'; -import { AddSource } from '../../../components/addSource/AddSource'; +import { AddInput } from '../../../components/addInput/AddInput'; import { IconX } from '@tabler/icons-react'; import { useSources } from '../../../hooks/sources/useSources'; import { @@ -722,14 +722,20 @@ export default function ProductionConfiguration({ params }: PageProps) { )} )} - { + onClickSource={() => { setInventoryVisible(true); }} + onClickHtml={() => { + /* */ + }} + onClickMediaplayer={() => { + /* */ + }} />
diff --git a/src/components/addInput/AddInput.tsx b/src/components/addInput/AddInput.tsx new file mode 100644 index 00000000..d8eded30 --- /dev/null +++ b/src/components/addInput/AddInput.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { + IconBrowserPlus, + IconPhotoPlus, + IconVideoPlus +} from '@tabler/icons-react'; +import { useTranslate } from '../../i18n/useTranslate'; + +type AddInputProps = { + onClickSource: () => void; + onClickHtml: () => void; + onClickMediaplayer: () => void; + disabled: boolean; +}; +export function AddInput({ + onClickSource, + onClickHtml, + onClickMediaplayer, + disabled +}: AddInputProps) { + const t = useTranslate(); + return ( +
+ + + +
+ ); +} diff --git a/src/components/addSource/AddSource.tsx b/src/components/addSource/AddSource.tsx deleted file mode 100644 index 9ed16fa3..00000000 --- a/src/components/addSource/AddSource.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { IconVideoPlus } from '@tabler/icons-react'; -import { useTranslate } from '../../i18n/useTranslate'; - -type AddSourceProps = { - onClick: () => void; - disabled: boolean; -}; -export function AddSource({ onClick, disabled }: AddSourceProps) { - const t = useTranslate(); - return ( - - ); -} diff --git a/src/hooks/productions.ts b/src/hooks/productions.ts index e3164fe9..cdaf4612 100644 --- a/src/hooks/productions.ts +++ b/src/hooks/productions.ts @@ -11,7 +11,8 @@ export function usePostProduction() { isActive: false, name, sources: [], - selectedPresetRef: undefined + html: [], + mediaplayers: [] }) }); if (response.ok) { diff --git a/src/interfaces/production.ts b/src/interfaces/production.ts index eb4d9655..b979a5ca 100644 --- a/src/interfaces/production.ts +++ b/src/interfaces/production.ts @@ -3,11 +3,25 @@ import { SourceReference } from './Source'; import { ControlConnection } from './controlConnections'; import { PipelineSettings } from './pipeline'; +interface HtmlReference { + _id: string; + input_slot: number; + label: string; +} + +interface MediaplayerReference { + _id: string; + input_slot: number; + label: string; +} + export interface Production { _id: string; isActive: boolean; name: string; sources: SourceReference[]; + html: HtmlReference[]; + mediaplayers: MediaplayerReference[]; production_settings: ProductionSettings; } diff --git a/src/middleware.ts b/src/middleware.ts index 7724e3b7..cac08478 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -41,4 +41,4 @@ export default withAuth(function middleware(req) { } }); -export const config = { matcher: ['/', '/((?!api|images).*)/'] }; +export const config = { matcher: ['/', '/((?!api|images|html_input).*)/'] };