diff --git a/src/api/ateliereLive/pipelines/renderingengine/renderingengine.ts b/src/api/ateliereLive/pipelines/renderingengine/renderingengine.ts index 15cb7428..c01c4b29 100644 --- a/src/api/ateliereLive/pipelines/renderingengine/renderingengine.ts +++ b/src/api/ateliereLive/pipelines/renderingengine/renderingengine.ts @@ -49,9 +49,15 @@ export async function createPipelineHtmlSource( data: HTMLSource, source: SourceReference ) { + const payload = { + height: Number(data.height), + input_slot: Number(inputSlot), + url: data.url || '', + width: Number(data.width) + }; + try { const { production_settings } = production; - const htmlResults = []; for (let i = 0; i < production_settings.pipelines.length; i++) { const response = await fetch( @@ -65,21 +71,34 @@ export async function createPipelineHtmlSource( headers: { authorization: getAuthorizationHeader() }, - body: JSON.stringify({ - height: Number(data.height), - input_slot: Number(inputSlot), - url: data.url || '', - width: Number(data.width) - }) + body: JSON.stringify(payload) } ); + const text = await response.text(); - if (response.ok) { - const text = await response.text(); - const jsonResponse = text ? JSON.parse(text) : {}; - htmlResults.push(jsonResponse); + if (response.status === 201) { + if (text.trim().length > 0) { + Log().warn('Unexpected content for 201 response:', text); + } + } else if ( + response.status === 400 || + response.status === 404 || + response.status === 500 + ) { + try { + const errorResponse = JSON.parse(text); + Log().error('API error response:', errorResponse); + throw new Error( + `API error ${response.status}: ${errorResponse.message}` + ); + } catch (parseError) { + Log().error('Failed to parse error response as JSON:', text); + throw new Error(`API error ${response.status}: ${text}`); + } } else { - throw await response.json(); + Log().error(`Unexpected status code ${response.status} received`); + Log().error('Response body:', text); + throw new Error(`Unexpected response status: ${response.status}`); } } } catch (e) { @@ -273,7 +292,11 @@ export async function deleteHtmlFromPipeline( ); if (response.ok) { const text = await response.text(); - return text ? JSON.parse(text) : {}; + + if (text) { + return JSON.parse(text); + } + return; } throw await response.json(); } @@ -308,9 +331,13 @@ export async function createPipelineMediaSource( data: MediaSource, source: SourceReference ) { + const payload = { + filename: data.filename, + input_slot: Number(inputSlot) + }; + try { const { production_settings } = production; - const mediaResults = []; for (let i = 0; i < production_settings.pipelines.length; i++) { const response = await fetch( @@ -324,18 +351,30 @@ export async function createPipelineMediaSource( headers: { authorization: getAuthorizationHeader() }, - body: JSON.stringify({ - filename: data.filename, - input_slot: Number(inputSlot) - }) + body: JSON.stringify(payload) } ); - if (response.ok) { - const text = await response.text(); - const jsonResponse = text ? JSON.parse(text) : {}; - mediaResults.push(jsonResponse); + const text = await response.text(); + + if (response.status === 201) { + if (text.trim().length > 0) { + Log().warn('Unexpected content for 201 response:', text); + } + } else if (response.status === 400 || response.status === 500) { + try { + const errorResponse = JSON.parse(text); + Log().error('API error response:', errorResponse); + throw new Error( + `API error ${response.status}: ${errorResponse.message}` + ); + } catch (parseError) { + Log().error('Failed to parse error response as JSON:', text); + throw new Error(`API error ${response.status}: ${text}`); + } } else { - throw await response.json(); + Log().error(`Unexpected status code ${response.status} received`); + Log().error('Response body:', text); + throw new Error(`Unexpected response status: ${response.status}`); } } } catch (e) { @@ -512,7 +551,11 @@ export async function deleteMediaFromPipeline( ); if (response.ok) { const text = await response.text(); - return text ? JSON.parse(text) : {}; + + if (text) { + return JSON.parse(text); + } + return; } throw await response.json(); } @@ -559,3 +602,89 @@ export async function getPipelineRenderingEngine( throw new Error(`Unexpected non-JSON response: ${responseText}`); } } + +export async function getPipelineRenderingEngineHtml( + pipelineUuid: string +): Promise { + const response = await fetch( + new URL( + LIVE_BASE_API_PATH + `/pipelines/${pipelineUuid}/renderingengine/html`, + process.env.LIVE_URL + ), + { + method: 'GET', + headers: { + authorization: getAuthorizationHeader() + }, + next: { + revalidate: 0 + } + } + ); + + if (response.ok) { + try { + return await response.json(); + } catch (error) { + console.error('Failed to parse successful JSON response:', error); + throw new Error('Parsing error in successful response.'); + } + } + + const contentType = response.headers.get('content-type'); + const responseText = await response.text(); + + if (contentType && contentType.includes('application/json')) { + try { + throw JSON.parse(responseText); + } catch (error) { + console.error('Failed to parse JSON error response:', error); + throw new Error(`Failed to parse JSON error response: ${responseText}`); + } + } else { + throw new Error(`Unexpected non-JSON response: ${responseText}`); + } +} + +export async function getPipelineRenderingEngineMedia( + pipelineUuid: string +): Promise { + const response = await fetch( + new URL( + LIVE_BASE_API_PATH + `/pipelines/${pipelineUuid}/renderingengine/media`, + process.env.LIVE_URL + ), + { + method: 'GET', + headers: { + authorization: getAuthorizationHeader() + }, + next: { + revalidate: 0 + } + } + ); + + if (response.ok) { + try { + return await response.json(); + } catch (error) { + console.error('Failed to parse successful JSON response:', error); + throw new Error('Parsing error in successful response.'); + } + } + + const contentType = response.headers.get('content-type'); + const responseText = await response.text(); + + if (contentType && contentType.includes('application/json')) { + try { + throw JSON.parse(responseText); + } catch (error) { + console.error('Failed to parse JSON error response:', error); + throw new Error(`Failed to parse JSON error response: ${responseText}`); + } + } else { + throw new Error(`Unexpected non-JSON response: ${responseText}`); + } +} diff --git a/src/api/manager/workflow.ts b/src/api/manager/workflow.ts index a9d7ca26..9cc24355 100644 --- a/src/api/manager/workflow.ts +++ b/src/api/manager/workflow.ts @@ -55,7 +55,8 @@ import { updatedMonitoringForProduction } from './job/syncMonitoring'; import { ObjectId } from 'mongodb'; import { MultiviewSettings } from '../../interfaces/multiview'; import { - getPipelineRenderingEngine, + getPipelineRenderingEngineHtml, + getPipelineRenderingEngineMedia, createPipelineHtmlSource, createPipelineMediaSource, deleteHtmlFromPipeline, @@ -342,12 +343,8 @@ export async function stopProduction( for (const pipeline of production.production_settings.pipelines) { const pipelineId = pipeline.pipeline_id; if (pipelineId) { - const pipelineRenderingEngine = await getPipelineRenderingEngine( - pipelineId - ); - - const htmlSources = pipelineRenderingEngine.html; - const mediaSources = pipelineRenderingEngine.media; + const htmlSources = await getPipelineRenderingEngineHtml(pipelineId); + const mediaSources = await getPipelineRenderingEngineMedia(pipelineId); if (htmlSources.length > 0 && htmlSources) { for (const pipeline of production.production_settings.pipelines) { @@ -810,7 +807,7 @@ export async function startProduction( if (htmlSource.html_data) { const htmlData = { ...htmlSource.html_data, - url: htmlSource.html_data?.url || '', + url: htmlSource.html_data.url || '', input_slot: htmlSource.input_slot }; await createPipelineHtmlSource( diff --git a/src/app/production/[id]/page.tsx b/src/app/production/[id]/page.tsx index b1ec1e36..b95c032d 100644 --- a/src/app/production/[id]/page.tsx +++ b/src/app/production/[id]/page.tsx @@ -70,7 +70,6 @@ import { useDeleteHtmlSource } from '../../../hooks/renderingEngine/useDeleteHtm import { useDeleteMediaSource } from '../../../hooks/renderingEngine/useDeleteMediaSource'; import { useCreateHtmlSource } from '../../../hooks/renderingEngine/useCreateHtmlSource'; import { useCreateMediaSource } from '../../../hooks/renderingEngine/useCreateMediaSource'; -import { useRenderingEngine } from '../../../hooks/renderingEngine/useRenderingEngine'; export default function ProductionConfiguration({ params }: PageProps) { const t = useTranslate(); @@ -146,7 +145,6 @@ export default function ProductionConfiguration({ params }: PageProps) { const [deleteMediaSource, deleteMediaLoading] = useDeleteMediaSource(); const [createHtmlSource, createHtmlLoading] = useCreateHtmlSource(); const [createMediaSource, createMediaLoading] = useCreateMediaSource(); - const [getRenderingEngine, renderingEngineLoading] = useRenderingEngine(); const { locked } = useContext(GlobalContext); @@ -202,8 +200,9 @@ export default function ProductionConfiguration({ params }: PageProps) { const selectedPresetCopy = cloneDeep(selectedPreset); const foundPipeline = selectedPresetCopy?.pipelines[pipelineIndex]; if (foundPipeline) { - foundPipeline.outputs = []; + foundPipeline.outputs = foundPipeline.outputs || []; foundPipeline.pipeline_name = pipelineName; + foundPipeline.pipeline_id = id; } setSelectedPreset(selectedPresetCopy); setProductionSetup((prevState) => { @@ -211,7 +210,8 @@ export default function ProductionConfiguration({ params }: PageProps) { if (!updatedPipelines) return; updatedPipelines[pipelineIndex].pipeline_name = pipelineName; updatedPipelines[pipelineIndex].pipeline_id = id; - updatedPipelines[pipelineIndex].outputs = []; + updatedPipelines[pipelineIndex].outputs = + prevState.production_settings.pipelines[pipelineIndex].outputs || []; putProduction(prevState._id, { ...prevState, production_settings: { @@ -235,7 +235,6 @@ export default function ProductionConfiguration({ params }: PageProps) { const production = config.isActive ? config : checkProductionPipelines(config, pipelines); - putProduction(production._id, production); setProductionSetup(production); setConfigurationName(production.name); @@ -339,7 +338,6 @@ export default function ProductionConfiguration({ params }: PageProps) { ); } } - putProduction(productionSetup?._id.toString(), updatedPreset).then(() => { refreshProduction(); }); @@ -400,7 +398,6 @@ export default function ProductionConfiguration({ params }: PageProps) { ) } }; - setProductionSetup(updatedProduction); }); } @@ -927,7 +924,6 @@ export default function ProductionConfiguration({ params }: PageProps) { const pipelineId = productionSetup.production_settings.pipelines[i].pipeline_id; if (pipelineId) { - getRenderingEngine(pipelineId); if (selectedSourceRef.type === 'html') { await deleteHtmlSource( pipelineId, @@ -1032,7 +1028,9 @@ export default function ProductionConfiguration({ params }: PageProps) { production={memoizedProduction} /> { + refreshProduction(); + }} production={productionSetup} disabled={ (!selectedPreset ? true : false) || @@ -1117,6 +1115,7 @@ export default function ProductionConfiguration({ params }: PageProps) { ); if (!updatedSetup) return; setProductionSetup(updatedSetup); + putProduction( updatedSetup._id.toString(), updatedSetup diff --git a/src/components/copyToClipboard/CopyItem.tsx b/src/components/copyToClipboard/CopyItem.tsx index fdbeca67..cc5f5475 100644 --- a/src/components/copyToClipboard/CopyItem.tsx +++ b/src/components/copyToClipboard/CopyItem.tsx @@ -41,9 +41,15 @@ export const CopyItem = ({ if ('clipboard' in navigator) { navigator.clipboard .writeText(valueToCopy) - .then(() => handleCopyResult('SUCCESS')) - .catch(() => handleCopyResult('ERROR')); + .then(() => { + handleCopyResult('SUCCESS'); + }) + .catch(() => { + console.log('Something went wrong copying:', valueToCopy); + handleCopyResult('ERROR'); + }); } else { + console.log('Clipboard API not available:', valueToCopy); handleCopyResult('ERROR'); } }; diff --git a/src/components/copyToClipboard/CopyToClipboard.tsx b/src/components/copyToClipboard/CopyToClipboard.tsx index 5ce47895..855c134d 100644 --- a/src/components/copyToClipboard/CopyToClipboard.tsx +++ b/src/components/copyToClipboard/CopyToClipboard.tsx @@ -1,6 +1,6 @@ 'use client'; -import React from 'react'; +import React, { useEffect } from 'react'; import CopyItem from './CopyItem'; import { WhepMultiview } from '../../interfaces/whep'; import { SrtOutput } from '../../interfaces/pipeline'; diff --git a/src/components/dropDown/PipelineNameDropDown.tsx b/src/components/dropDown/PipelineNameDropDown.tsx index a236a215..8dd0b959 100644 --- a/src/components/dropDown/PipelineNameDropDown.tsx +++ b/src/components/dropDown/PipelineNameDropDown.tsx @@ -24,7 +24,9 @@ export default function PipelineNamesDropDown({ const [selected, setSelected] = useState(initial); useEffect(() => { const id = options?.find((o) => o.option === selected)?.id; - setSelectedPipelineName(pipelineIndex, selected, id); + if (selected && id) { + setSelectedPipelineName(pipelineIndex, selected, id); + } }, [selected]); const handleSetSelected = (option: string) => { diff --git a/src/components/modal/configureMultiviewModal/ConfigureMultiviewModal.tsx b/src/components/modal/configureMultiviewModal/ConfigureMultiviewModal.tsx index 11b2fac3..bdb79573 100644 --- a/src/components/modal/configureMultiviewModal/ConfigureMultiviewModal.tsx +++ b/src/components/modal/configureMultiviewModal/ConfigureMultiviewModal.tsx @@ -137,6 +137,7 @@ export function ConfigureMultiviewModal({ return; } + console.log('production ID - to save', newMultiviewLayout?._id); await addNewLayout(newMultiviewLayout); setNewMultiviewLayout(null); setLayoutModalOpen(false); diff --git a/src/components/modal/configureMultiviewModal/MultiviewLayoutSettings/MultiviewLayoutSettings.tsx b/src/components/modal/configureMultiviewModal/MultiviewLayoutSettings/MultiviewLayoutSettings.tsx index f5e6db13..0d732fa2 100644 --- a/src/components/modal/configureMultiviewModal/MultiviewLayoutSettings/MultiviewLayoutSettings.tsx +++ b/src/components/modal/configureMultiviewModal/MultiviewLayoutSettings/MultiviewLayoutSettings.tsx @@ -60,6 +60,10 @@ export default function MultiviewLayoutSettings({ newPresetName ); + useEffect(() => { + console.log('production ID - main layout', production?._id); + }, [production?._id]); + const deleteLayout = useDeleteMultiviewLayout(); const t = useTranslate(); diff --git a/src/components/pipeline/PipelineCard.tsx b/src/components/pipeline/PipelineCard.tsx index 1349a18c..7e2c7189 100644 --- a/src/components/pipeline/PipelineCard.tsx +++ b/src/components/pipeline/PipelineCard.tsx @@ -18,6 +18,7 @@ export function PipelineCard({ pipelineId, isActive }: PipelineCardProps) { }, [isActive]); const SrtInfo = data?.status; + if (SrtInfo === undefined) { return null; } diff --git a/src/components/startProduction/ConfigureOutputButton.tsx b/src/components/startProduction/ConfigureOutputButton.tsx index 8bcf015a..30761801 100644 --- a/src/components/startProduction/ConfigureOutputButton.tsx +++ b/src/components/startProduction/ConfigureOutputButton.tsx @@ -6,7 +6,6 @@ import { Preset } from '../../interfaces/preset'; import { useTranslate } from '../../i18n/useTranslate'; import { Button } from '../button/Button'; import { ConfigureOutputModal } from '../modal/configureOutputModal/ConfigureOutputModal'; -import { PipelineSettings } from '../../interfaces/pipeline'; type ConfigureOutputButtonProps = { preset?: Preset; diff --git a/src/hooks/productions.ts b/src/hooks/productions.ts index f3e26867..c6900cb5 100644 --- a/src/hooks/productions.ts +++ b/src/hooks/productions.ts @@ -27,7 +27,8 @@ export function useGetProduction() { headers: [['x-api-key', `Bearer ${API_SECRET_KEY}`]] }); if (response.ok) { - return response.json(); + const res = response.json(); + return res; } throw await response.text(); }; diff --git a/src/hooks/useConfigureMultiviewLayout.tsx b/src/hooks/useConfigureMultiviewLayout.tsx index 1338d1a3..de7ca354 100644 --- a/src/hooks/useConfigureMultiviewLayout.tsx +++ b/src/hooks/useConfigureMultiviewLayout.tsx @@ -16,6 +16,7 @@ export function useConfigureMultiviewLayout( useEffect(() => { if (productionId && preset && (defaultLabel || source)) { + console.log('production ID - layout hook', productionId); const arr: MultiviewViews[] = []; preset.layout.views.map((item, index) => { if (index.toString() === viewId) {