Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solves the problem with starting productions with websocket-sources #15

Merged
merged 43 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
fa006bf
feat: support media and html sources in prod and web sockets
Saelmala Aug 23, 2024
b9f4c4c
WIP! feat: add media & html websocket connections
bwallberg Aug 26, 2024
d1f6cbf
feat: support to add media player and html sources
Saelmala Sep 3, 2024
8b9d4ae
fix: fix drag and drop and lint
Saelmala Sep 4, 2024
03b6a7d
WIP! feat: add media & html websocket connections
bwallberg Aug 26, 2024
ca1f8e6
feat: support to add media player and html sources
Saelmala Sep 3, 2024
ca856ef
fix: fix drag and drop and lint
Saelmala Sep 4, 2024
6b5ee11
feat: add additional filtering
Saelmala Aug 23, 2024
474d203
fix: handle active + source filter, reduce code
Saelmala Aug 23, 2024
77ba4e1
fix: linting
Saelmala Aug 23, 2024
aaf3069
fix: add ingest type to database
Saelmala Aug 26, 2024
93f9048
feat: first commit of source-delete, with basic styling and fetches
malmen237 Aug 28, 2024
0acd37a
feat: added creation-date and function to set correct status when get…
malmen237 Aug 30, 2024
9d19b68
feat: updated server-fetch to be put, not possible to delete
malmen237 Aug 30, 2024
ae74ea9
feat: moved logic out of component to inventory-page and added purge-…
malmen237 Aug 30, 2024
ebb2d20
fix: simplified the delete-source-hook
malmen237 Aug 30, 2024
89d6ec8
fix: rebase to main
malmen237 Sep 5, 2024
f3ecb17
fix: removed created-at and used last-connected instead
malmen237 Sep 5, 2024
88ff051
fix: added better error-handling and made more logical naming of code
malmen237 Sep 5, 2024
824d700
fix: corrected misspelling
malmen237 Sep 5, 2024
0389ac5
WIP! feat: add media & html websocket connections
bwallberg Aug 26, 2024
1f579c5
fix: able to start prod w wevsocket sources
malmen237 Sep 11, 2024
0f2d040
fix: solved errors and conflicts
malmen237 Sep 11, 2024
13772e4
fix: added missing types
malmen237 Sep 11, 2024
bcdb35b
fix: lint error
malmen237 Sep 11, 2024
c60a478
fix: solved conflicts and problems with 'media-html-inputs'-branch
malmen237 Sep 12, 2024
230837f
fix: updated code, with dnd not working
malmen237 Sep 12, 2024
975bebb
fix: orders dnd correct when starting production
malmen237 Sep 12, 2024
f9fc4af
fix: add back previous functionality and empty slot card
Saelmala Sep 13, 2024
fd63506
fix: can remove html/mediaplayer during production
Saelmala Sep 13, 2024
ab1c93a
fix: use correct input slot
Saelmala Sep 17, 2024
4575dbc
fix: clean up input_slot definition
Saelmala Sep 17, 2024
a093a3b
fix: bug-fix black screen multiview
Saelmala Sep 18, 2024
2ece8ea
fix: move websocket reset and play/load
Saelmala Sep 18, 2024
015fcc5
fix: disable select if no selected config
Saelmala Sep 19, 2024
96e6907
Merge branch 'main' into fix/start-prod-w-websocket
Saelmala Sep 19, 2024
0a0fb25
fix: bug-solve and addressed feedback
Saelmala Sep 20, 2024
e49f5ba
fix: test fail
Saelmala Sep 20, 2024
62ea4b4
fix: build fail
Saelmala Sep 20, 2024
963f016
fix: remove non-null assertor
Saelmala Sep 20, 2024
6247316
fix: delete unused file
Saelmala Sep 23, 2024
07931c9
fix: code cleanup
Saelmala Sep 23, 2024
a77e252
Merge branch 'main' into fix/start-prod-w-websocket
Saelmala Sep 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ MONGODB_URI=${MONGODB_URI:-mongodb://api:password@localhost:27017/live-gui}
# Ateliere Live System Controlleer
LIVE_URL=${LIVE_URL:-https://localhost:8080}
LIVE_CREDENTIALS=${LIVE_CREDENTIALS:-admin:admin}
CONTROL_PANEL_WS==${}
# This ENV variable disables SSL Verification, use if the above LIVE_URL doesn't have a proper certificate
NODE_TLS_REJECT_UNAUTHORIZED=${NODE_TLS_REJECT_UNAUTHORIZED:-1}

Expand All @@ -14,3 +15,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
7 changes: 3 additions & 4 deletions src/api/ateliereLive/pipelines/multiviews/multiviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,12 @@ export async function createMultiviewForPipeline(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
productionSettings.pipelines[multiviewIndex].pipeline_id!;
const sources = await getSourcesByIds(
sourceRefs.map((ref) => ref._id.toString())
sourceRefs.map((ref) => (ref._id ? ref._id.toString() : ''))
);
const sourceRefsWithLabels = sourceRefs.map((ref) => {
const refId = ref._id ? ref._id.toString() : '';
if (!ref.label) {
const source = sources.find(
(source) => source._id.toString() === ref._id.toString()
);
const source = sources.find((source) => source._id.toString() === refId);
ref.label = source?.name || '';
}
return ref;
Expand Down
34 changes: 20 additions & 14 deletions src/api/ateliereLive/pipelines/streams/streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export async function createStream(
return pipeline.uuid;
})
);

const ingestUuid = await getUuidFromIngestName(
source.ingest_name,
false
Expand All @@ -79,13 +80,15 @@ export async function createStream(
source.ingest_source_name,
false
);

const audioMapping =
source.audio_stream.audio_mapping &&
source.audio_stream.audio_mapping.length > 0
? source.audio_stream.audio_mapping
: [[0, 1]];

await initDedicatedPorts();

for (const pipeline of production_settings.pipelines) {
const availablePorts = getAvailablePortsForIngest(
source.ingest_name,
Expand All @@ -101,28 +104,29 @@ export async function createStream(
Log().info(
`Allocated port ${availablePort} on '${source.ingest_name}' for ${source.ingest_source_name}`
);

const stream: PipelineStreamSettings = {
ingest_id: ingestUuid,
source_id: sourceId,
pipeline_id: pipeline.pipeline_id!,
input_slot: input_slot,
alignment_ms: pipeline.alignment_ms,
audio_format: pipeline.audio_format,
audio_sampling_frequency: pipeline.audio_sampling_frequency,
bit_depth: pipeline.bit_depth,
convert_color_range: pipeline.convert_color_range,
encoder: pipeline.encoder,
encoder_device: pipeline.encoder_device,
format: pipeline.format,
max_network_latency_ms: pipeline.max_network_latency_ms,
width: pipeline.width,
height: pipeline.height,
frame_rate_d: pipeline.frame_rate_d,
frame_rate_n: pipeline.frame_rate_n,
format: pipeline.format,
encoder: pipeline.encoder,
encoder_device: pipeline.encoder_device,
gop_length: pipeline.gop_length,
height: pipeline.height,
max_network_latency_ms: pipeline.max_network_latency_ms,
pic_mode: pipeline.pic_mode,
speed_quality_balance: pipeline.speed_quality_balance,
video_kilobit_rate: pipeline.video_kilobit_rate,
width: pipeline.width,
ingest_id: ingestUuid,
source_id: sourceId,
input_slot,
bit_depth: pipeline.bit_depth,
speed_quality_balance: pipeline.speed_quality_balance,
convert_color_range: pipeline.convert_color_range,
audio_sampling_frequency: pipeline.audio_sampling_frequency,
audio_format: pipeline.audio_format,
audio_mapping: JSON.stringify(audioMapping),
interfaces: [
{
Expand All @@ -131,6 +135,7 @@ export async function createStream(
}
]
};

try {
Log().info(
`Connecting '${source.ingest_name}/${ingestUuid}}:${source.ingest_source_name}' to '${pipeline.pipeline_name}/${pipeline.pipeline_id}'`
Expand All @@ -147,6 +152,7 @@ export async function createStream(
Log().info(
`Stream '${result.stream_uuid}' from '${source.ingest_name}/${ingestUuid}' to '${pipeline.pipeline_name}/${pipeline.pipeline_id}' connected`
);

sourceToPipelineStreams.push({
source_id: source._id.toString(),
stream_uuid: result.stream_uuid,
Expand Down
40 changes: 40 additions & 0 deletions src/api/ateliereLive/websocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import WebSocket from 'ws';

function createWebSocket(): Promise<WebSocket> {
return new Promise((resolve, reject) => {
const ws = new WebSocket(`ws://${process.env.CONTROL_PANEL_WS}`);
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 create ${input} 1920 1080`);
},
createMediaplayer: (input: number) => {
ws.send(`media create ${input} ${process.env.MEDIAPLAYER_PLACEHOLDER}`);
},
closeHtml: (input: number) => {
ws.send(`html close ${input}`);
ws.send('html reset');
},
closeMediaplayer: (input: number) => {
ws.send(`media close ${input}`);
ws.send('media reset');
},
close: () =>
setTimeout(() => {
ws.close();
}, 1000)
};
}
27 changes: 25 additions & 2 deletions src/api/manager/productions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,44 @@ export async function setProductionsIsActiveFalse(): Promise<
export async function putProduction(
id: string,
production: Production
): Promise<void> {
): Promise<Production> {
const db = await getDatabase();
const newSourceId = new ObjectId().toString();

const sources = production.sources
? production.sources.flatMap((singleSource) => {
return singleSource._id
? singleSource
: {
_id: newSourceId,
type: singleSource.type,
label: singleSource.label,
input_slot: singleSource.input_slot
};
})
: [];

await db.collection('productions').findOneAndReplace(
{ _id: new ObjectId(id) },
{
name: production.name,
isActive: production.isActive,
sources: production.sources,
sources: sources,
production_settings: production.production_settings
}
);

if (!production.isActive) {
deleteMonitoring(db, id);
}

return {
_id: new ObjectId(id).toString(),
name: production.name,
isActive: production.isActive,
sources: sources,
production_settings: production.production_settings
};
}

export async function postProduction(data: Production): Promise<ObjectId> {
Expand Down
54 changes: 31 additions & 23 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, WithId } from 'mongodb';
import { getDatabase } from '../mongoClient/dbClient';

export function getMockedSources() {
Expand All @@ -9,37 +9,45 @@ 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[]) {
export async function getSourcesByIds(
_ids: string[]
): Promise<WithId<Source>[]> {
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");
});
const objectIds = _ids.map((id: string) => new ObjectId(id));

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 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
Loading
Loading