Skip to content

Commit

Permalink
feat: support to add media player and html sources
Browse files Browse the repository at this point in the history
  • Loading branch information
Saelmala committed Sep 3, 2024
1 parent cb78392 commit f229224
Show file tree
Hide file tree
Showing 14 changed files with 372 additions and 143 deletions.
10 changes: 6 additions & 4 deletions src/api/agileLive/pipelines/multiviews/multiviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ export async function createMultiviewForPipeline(
}
const pipelineUUID =
productionSettings.pipelines[multiviewIndex].pipeline_id!;
const sources = await getSourcesByIds(
sourceRefs.map((ref) => ref._id.toString())
);
const sources = await getSourcesByIds(
sourceRefs
.filter((ref) => ref._id !== undefined)
.map((ref) => ref._id!.toString())
);
const sourceRefsWithLabels = sourceRefs.map((ref) => {
if (!ref.label) {
const source = sources.find(
(source) => source._id.toString() === ref._id.toString()
(source) => source._id.toString() === ref._id?.toString()
);
ref.label = source?.name || '';
}
Expand Down
28 changes: 13 additions & 15 deletions src/api/agileLive/websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,31 @@ function createWebSocket(): Promise<WebSocket> {
});
}

export async function createHtmlWebSocket() {
export async function createControlPanelWebSocket() {
const ws = await createWebSocket();
return {
create: (input: number) => {
createHtml: (input: number) => {
ws.send('html reset');
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) => {
createMediaplayer: (input: number) => {
ws.send('media reset');
ws.send(`media create ${input} ${process.env.MEDIAPLAYER_PLACEHOLDER}`);
ws.send(`media play ${input}`);
},
close: (input: number) => {
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
26 changes: 23 additions & 3 deletions src/api/manager/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { Result } from '../../interfaces/result';
import { Monitoring } from '../../interfaces/monitoring';
import { getDatabase } from '../mongoClient/dbClient';
import { updatedMonitoringForProduction } from './job/syncMonitoring';
import { createControlPanelWebSocket } from '../agileLive/websocket';

const isUsed = (pipeline: ResourcesPipelineResponse) => {
const hasStreams = pipeline.streams.length > 0;
Expand Down Expand Up @@ -308,6 +309,10 @@ 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 +321,9 @@ 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 @@ -448,9 +456,21 @@ 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") {
Expand Down Expand Up @@ -716,7 +736,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
50 changes: 38 additions & 12 deletions src/app/production/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
AddSourceStatus,
DeleteSourceStatus,
SourceReference,
SourceWithId
SourceWithId,
Type
} from '../../../interfaces/Source';
import { useGetProduction, usePutProduction } from '../../../hooks/productions';
import { Production } from '../../../interfaces/production';
Expand Down Expand Up @@ -40,8 +41,8 @@ import { RemoveSourceModal } from '../../../components/modal/RemoveSourceModal';
import { useDeleteStream, useCreateStream } from '../../../hooks/streams';
import { MonitoringButton } from '../../../components/button/MonitoringButton';
import { useGetMultiviewPreset } from '../../../hooks/multiviewPreset';
import { ISource } from '../../../hooks/useDragableItems';
import { useMultiviews } from '../../../hooks/multiviews';
import { v4 as uuidv4 } from 'uuid';

export default function ProductionConfiguration({ params }: PageProps) {
const t = useTranslate();
Expand All @@ -59,6 +60,8 @@ export default function ProductionConfiguration({ params }: PageProps) {
const [selectedSourceRef, setSelectedSourceRef] = useState<
SourceReference | undefined
>();
const [sourceReferenceToAdd, setSourceReferenceToAdd] =
useState<SourceReference>();
const [createStream, loadingCreateStream] = useCreateStream();
const [deleteStream, loadingDeleteStream] = useDeleteStream();
//PRODUCTION
Expand Down Expand Up @@ -93,6 +96,29 @@ export default function ProductionConfiguration({ params }: PageProps) {
refreshControlPanels();
}, [productionSetup?.isActive]);

// TODO: Väldigt lik den för ingest_source --> ändra??
const addSourceToProduction = (type: Type) => {
const newSource: SourceReference = {
_id: uuidv4(),
type: type,
label: type === 'html' ? 'HTML Input' : 'Media Player Source',
input_slot: getFirstEmptySlot()
};
setSourceReferenceToAdd(newSource);

if (productionSetup) {
const updatedSetup = addSetupItem(newSource, productionSetup);
if (!updatedSetup) return;
setProductionSetup(updatedSetup);
putProduction(updatedSetup._id.toString(), updatedSetup).then(() => {
refreshProduction();
setAddSourceModal(false);
setSourceReferenceToAdd(undefined);
});
setAddSourceStatus(undefined);
}
};

const setSelectedControlPanel = (controlPanel: string[]) => {
setProductionSetup((prevState) => {
if (!prevState) return;
Expand Down Expand Up @@ -371,6 +397,7 @@ export default function ProductionConfiguration({ params }: PageProps) {
const updatedSetup = addSetupItem(
{
_id: source._id.toString(),
type: 'ingest_source',
label: source.ingest_source_name,
input_slot: getFirstEmptySlot()
},
Expand Down Expand Up @@ -442,8 +469,9 @@ export default function ProductionConfiguration({ params }: PageProps) {
}
if (result.ok) {
if (result.value.success) {
const sourceToAdd = {
const sourceToAdd: SourceReference = {
_id: result.value.streams[0].source_id,
type: 'ingest_source',
label: selectedSource.name,
stream_uuids: result.value.streams.map((r) => r.stream_uuid),
input_slot: getFirstEmptySlot()
Expand All @@ -464,11 +492,13 @@ export default function ProductionConfiguration({ params }: PageProps) {
}
};

// TODO: HTML och MEDIA PLAYER KÄLLOR TAS INTE BORT
const handleRemoveSource = async () => {
if (
productionSetup &&
productionSetup.isActive &&
selectedSourceRef &&
// Gör det här att sourcen inte tas bort ordentligt?
selectedSourceRef.stream_uuids
) {
const multiview =
Expand Down Expand Up @@ -674,15 +704,12 @@ export default function ProductionConfiguration({ params }: PageProps) {
{productionSetup?.sources && sources.size > 0 && (
<DndProvider backend={HTML5Backend}>
<SourceCards
sourceRef={sourceReferenceToAdd}
productionSetup={productionSetup}
updateProduction={(updated) => {
updateProduction(productionSetup._id, updated);
}}
onSourceUpdate={(
source: SourceReference,
sourceItem: ISource
) => {
sourceItem.label = source.label;
onSourceUpdate={(source: SourceReference) => {
updateSource(source, productionSetup);
}}
onSourceRemoval={(source: SourceReference) => {
Expand All @@ -693,6 +720,7 @@ export default function ProductionConfiguration({ params }: PageProps) {
const updatedSetup = removeSetupItem(
{
_id: source._id,
type: source.type,
label: source.label,
input_slot: source.input_slot
},
Expand Down Expand Up @@ -730,11 +758,9 @@ export default function ProductionConfiguration({ params }: PageProps) {
onClickSource={() => {
setInventoryVisible(true);
}}
onClickHtml={() => {
/* */
}}
onClickHtml={() => addSourceToProduction('html')}
onClickMediaplayer={() => {
/* */
addSourceToProduction('mediaplayer');
}}
/>
</div>
Expand Down
51 changes: 30 additions & 21 deletions src/components/dragElement/DragItem.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { ReactElement, memo, useRef } from 'react';
import React, { ReactElement, memo, useEffect, useRef } from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { SourceReference } from '../../interfaces/Source';
import { ObjectId } from 'mongodb';
import { Production } from '../../interfaces/production';
import { v4 as uuidv4 } from 'uuid';

interface IDrag {
id: ObjectId;
id: ObjectId | string;
selectingText: boolean;
onMoveItem: (currentId: string, nextId: string) => void;
children: ReactElement;
Expand All @@ -14,11 +15,10 @@ interface IDrag {
updateProduction: (updated: Production) => void;
productionSetup: Production;
}

const DragItem: React.FC<IDrag> = memo(
({
id,
selectingText: selectingText,
selectingText,
onMoveItem,
children,
previousOrder,
Expand All @@ -28,34 +28,43 @@ const DragItem: React.FC<IDrag> = memo(
}) => {
const ref = useRef(null);

const oid = typeof id === 'string' ? id : id.toString();

const [{ isDragging }, connectDrag] = useDrag({
canDrag: !selectingText,
type: 'Card',
item: { id },
collect: (monitor) => {
return {
isDragging: monitor.isDragging()
};
}
item: { oid },
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
});

const [, connectDrop] = useDrop({
accept: 'Card',
hover(hoveredOverItem: { id: ObjectId }) {
if (hoveredOverItem.id !== id && id) {
onMoveItem(hoveredOverItem.id.toString(), id.toString());
hover(hoveredOverItem: { oid: string }) {
if (hoveredOverItem.oid !== oid) {
onMoveItem(hoveredOverItem.oid.toString(), oid.toString());
}
},
drop() {
const isSame = previousOrder.every(
(item, index) => item._id === currentOrder[index]._id
);
const isSameLength = previousOrder.length === currentOrder.length;
const isSame = isSameLength
? previousOrder.every(
(item, index) => item._id === currentOrder[index]?._id
)
: false;

if (!isSame) {
console.log('ORDER CHANGED');

const updatedProduction = {
...productionSetup,
sources: currentOrder
sources: currentOrder.map((source) => ({
...source,
_id: source._id || uuidv4() // Ensure ID consistency
}))
};

updateProduction(updatedProduction);
}
}
Expand All @@ -68,12 +77,12 @@ const DragItem: React.FC<IDrag> = memo(

return (
<>
{React.Children.map(children, (child) => {
return React.cloneElement(child, {
{React.Children.map(children, (child) =>
React.cloneElement(child, {
forwardedRef: ref,
style: containerStyle
});
})}
})
)}
</>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/modal/AddSourceModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ export function AddSourceModal({
return (
<Modal open={open} outsideClick={onAbort}>
<div className="text-center flex flex-col items-center">
<p>HEJ</p>
<h1 className="text-xl">{t('workflow.add_source_modal', { name })}</h1>

<div>{status && <AddSourceFeed status={status} />}</div>
<div className="flex gap-8 mt-4">
<Button
Expand Down
Loading

0 comments on commit f229224

Please sign in to comment.