Skip to content

Commit

Permalink
feat: support media and html sources in prod and web sockets
Browse files Browse the repository at this point in the history
  • Loading branch information
Saelmala authored and birme committed Sep 11, 2024
1 parent 3f5723a commit fa006bf
Show file tree
Hide file tree
Showing 27 changed files with 602 additions and 281 deletions.
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -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
32 changes: 31 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
45 changes: 45 additions & 0 deletions src/api/ateliereLive/websocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import WebSocket from 'ws';

function createWebSocket(): Promise<WebSocket> {
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 createControlPanelWebSocket() {
const ws = await createWebSocket();
return {
createHtml: (input: number) => {
ws.send('html reset');
ws.send(`html create ${input} 1920 1080`);
setTimeout(() => {
ws.send(
`html load ${input} ${process.env.NEXTAUTH_URL}/html_input?input=${input}`
);
}, 1000);
},
createMediaplayer: (input: number) => {
ws.send('media reset');
ws.send(`media create ${input} ${process.env.MEDIAPLAYER_PLACEHOLDER}`);
ws.send(`media play ${input}`);
},
closeHtml: (input: number) => {
ws.send(`html close ${input}`);
},
closeMediaplayer: (input: number) => {
ws.send(`media close ${input}`);
},
close: () => {
ws.close();
}
};
}
1 change: 1 addition & 0 deletions src/api/manager/productions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Db, ObjectId, UpdateResult } from 'mongodb';
import { getDatabase } from '../mongoClient/dbClient';
import { Production, ProductionWithId } from '../../interfaces/production';
import { Log } from '../logger';
import { SourceReference, Type } from '../../interfaces/Source';

export async function getProductions(): Promise<Production[]> {
const db = await getDatabase();
Expand Down
51 changes: 29 additions & 22 deletions src/api/manager/sources.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import inventory from './mocks/inventory.json';
import { Source } from '../../interfaces/Source';
import { ObjectId } from 'mongodb';
import { ObjectId, OptionalId } from 'mongodb';
import { getDatabase } from '../mongoClient/dbClient';

export function getMockedSources() {
Expand All @@ -9,37 +9,44 @@ export function getMockedSources() {

export async function postSource(data: Source): Promise<ObjectId> {
const db = await getDatabase();
return (await db.collection('inventory').insertOne(data))
.insertedId as ObjectId;
const insertData: OptionalId<Omit<Source, '_id'>> & { _id?: ObjectId } = {
...data,
_id: typeof data._id === 'string' ? new ObjectId(data._id) : data._id
};
const result = await db.collection('inventory').insertOne(insertData);
return result.insertedId as ObjectId;
}

export async function getSources() {
const db = await getDatabase();
return await db.collection<Source>('inventory').find().toArray();
}

export async function getSourcesByIds(_ids: string[]) {
const db = await getDatabase().catch(() => {
throw "Can't connect to Database";
});
const objectIds = _ids.map((id: string) => {
return new ObjectId(id);
throw new Error("Can't connect to Database");
});

return (
await db
.collection<Source>('inventory')
.find({
_id: {
$in: objectIds
}
})
.toArray()
).sort(
(a, b) =>
_ids.findIndex((id) => a._id.equals(id)) -
_ids.findIndex((id) => b._id.equals(id))
);
const objectIds = _ids.map((id: string) => new ObjectId(id));

const sources = await db
.collection<Source>('inventory')
.find({
_id: {
$in: objectIds
}
})
.toArray();

return sources.sort((a, b) => {
const findIndex = (id: ObjectId | string) =>
_ids.findIndex((originalId) =>
id instanceof ObjectId
? id.equals(new ObjectId(originalId))
: id === originalId
);

return findIndex(a._id) - findIndex(b._id);
});
}

export async function updateSource(source: any) {
Expand Down
45 changes: 40 additions & 5 deletions src/api/manager/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import { Result } from '../../interfaces/result';
import { Monitoring } from '../../interfaces/monitoring';
import { getDatabase } from '../mongoClient/dbClient';
import { updatedMonitoringForProduction } from './job/syncMonitoring';
import { createControlPanelWebSocket } from '../ateliereLive/websocket';
import { ObjectId } from 'mongodb';

const isUsed = (pipeline: ResourcesPipelineResponse) => {
const hasStreams = pipeline.streams.length > 0;
Expand Down Expand Up @@ -89,7 +91,7 @@ async function connectIngestSources(
source.ingest_source_name,
false
);
const audioSettings = await getAudioMapping(source._id);
const audioSettings = await getAudioMapping(new ObjectId(source._id));
const newAudioMapping = audioSettings?.audio_stream?.audio_mapping;
const audioMapping = newAudioMapping?.length ? newAudioMapping : [[0, 1]];

Expand Down Expand Up @@ -308,6 +310,14 @@ export async function stopProduction(
(p) => p.pipeline_id
);

const controlPanelWS = await createControlPanelWebSocket();
const htmlSources = production.sources.filter(
(source) => source.type === 'html'
);
const mediaPlayerSources = production.sources.filter(
(source) => source.type === 'mediaplayer'
);

for (const source of production.sources) {
for (const stream_uuid of source.stream_uuids || []) {
await deleteStreamByUuid(stream_uuid).catch((error) => {
Expand All @@ -316,6 +326,11 @@ export async function stopProduction(
}
}

htmlSources.map((source) => controlPanelWS.closeHtml(source.input_slot));
mediaPlayerSources.map((source) =>
controlPanelWS.closeMediaplayer(source.input_slot)
);

for (const id of pipelineIds) {
Log().info(`Stopping pipeline '${id}'`);
if (!id) continue;
Expand Down Expand Up @@ -449,10 +464,30 @@ export async function startProduction(
// Try to setup streams from ingest(s) to pipeline(s) start
try {
// Get sources from the DB
// Skapa en createHtmlWebSocket, spara
const controlPanelWS = await createControlPanelWebSocket();
const htmlSources = production.sources.filter(
(source) => source.type === 'html'
);
const mediaPlayerSources = production.sources.filter(
(source) => source.type === 'mediaplayer'
);

htmlSources.map((source) => controlPanelWS.createHtml(source.input_slot));
mediaPlayerSources.map((source) =>
controlPanelWS.createMediaplayer(source.input_slot)
);

controlPanelWS.close();

// Nedan behöver göras efter att vi har skapat en produktion
// TODO: Hämta production.sources, för varje html-reference --> create i createHtmlWebSocket, för varje mediaplayer i production.sources skapa en createWebSocket
const sources = await getSourcesByIds(
production.sources.map((source) => {
return source._id.toString();
})
production.sources
.filter((source) => source._id !== undefined)
.map((source) => {
return source._id!.toString();
})
).catch((error) => {
if (error === "Can't connect to Database") {
throw "Can't connect to Database";
Expand Down Expand Up @@ -720,7 +755,7 @@ export async function startProduction(
...production,
sources: production.sources.map((source) => {
const streamsForSource = streams?.filter(
(stream) => stream.source_id === source._id.toString()
(stream) => stream.source_id === source._id?.toString()
);
return {
...source,
Expand Down
10 changes: 10 additions & 0 deletions src/app/html_input/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { PageProps } from '../../../.next/types/app/html_input/page';

export default function HtmlInput({ searchParams: { input } }: PageProps) {
return (
<div className="fixed top-0 left-0 h-screen w-screen bg-white flex flex-col gap-12 justify-center items-center">
<p className="text-9xl font-extrabold">HTML INPUT</p>
<p className="text-8xl font-bold">{input}</p>
</div>
);
}
Loading

0 comments on commit fa006bf

Please sign in to comment.