From 474296747355a7a721925bfe31507bd2ca2c0a8b Mon Sep 17 00:00:00 2001 From: Bernard Gawor Date: Thu, 10 Sep 2026 11:50:32 +0200 Subject: [PATCH 1/2] Regenerate the composition client against the current spec The register-input response now reports publish_url in place of port. --- .../src/generated/models/RegisterInputResponse.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/composition-openapi/src/generated/models/RegisterInputResponse.ts b/packages/composition-openapi/src/generated/models/RegisterInputResponse.ts index febbebac..92cf5e69 100644 --- a/packages/composition-openapi/src/generated/models/RegisterInputResponse.ts +++ b/packages/composition-openapi/src/generated/models/RegisterInputResponse.ts @@ -45,10 +45,10 @@ export interface RegisterInputResponse { audioDurationMs?: number | null; /** * - * @type {number} + * @type {string} * @memberof RegisterInputResponse */ - port?: number | null; + publishUrl?: string | null; } /** @@ -87,7 +87,8 @@ export function RegisterInputResponseFromJSONTyped(json: any, ignoreDiscriminato : json['audio_duration_ms'] === null ? null : json['audio_duration_ms'], - port: json['port'] === undefined ? undefined : json['port'] === null ? null : json['port'], + publishUrl: + json['publish_url'] === undefined ? undefined : json['publish_url'] === null ? null : json['publish_url'], }; } @@ -108,6 +109,6 @@ export function RegisterInputResponseToJSONTyped( endpoint_route: value['endpointRoute'], video_duration_ms: value['videoDurationMs'], audio_duration_ms: value['audioDurationMs'], - port: value['port'], + publish_url: value['publishUrl'], }; } From da514cc69d4eaecc0e9057fe1c6e35662ee40055 Mon Sep 17 00:00:00 2001 From: Bernard Gawor Date: Thu, 10 Sep 2026 11:54:56 +0200 Subject: [PATCH 2/2] Return the RTMP publishing address from registerRtmpInput The server now reports a complete publish URL, so the caller no longer has to build one from the composition origin and a port. --- packages/js-server-sdk/src/composition.ts | 15 ++++++++++--- .../js-server-sdk/tests/composition.test.ts | 22 ++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/js-server-sdk/src/composition.ts b/packages/js-server-sdk/src/composition.ts index 874e676a..1606474b 100644 --- a/packages/js-server-sdk/src/composition.ts +++ b/packages/js-server-sdk/src/composition.ts @@ -209,14 +209,23 @@ export class CompositionClient { /** * Register an input that an RTMP publisher pushes media into. The stream key identifies the - * input; the address to publish to belongs to the composition, not to this call. + * input and is carried in the returned address. + * @returns the address to publish the RTMP stream to */ async registerRtmpInput( compositionId: CompositionId, inputId: InputId, options: Omit - ): Promise { - await this.registerInput(compositionId, inputId, { ...options, type: 'rtmp_server' }); + ): Promise { + const { publishUrl } = await this.registerInput(compositionId, inputId, { ...options, type: 'rtmp_server' }); + + if (!publishUrl) { + throw new UnknownException({ + message: `Could not obtain a publishing address for input "${inputId}", retry or report it`, + }); + } + + return publishUrl; } /** diff --git a/packages/js-server-sdk/tests/composition.test.ts b/packages/js-server-sdk/tests/composition.test.ts index 9ff3e7ea..9697ecd0 100644 --- a/packages/js-server-sdk/tests/composition.test.ts +++ b/packages/js-server-sdk/tests/composition.test.ts @@ -67,13 +67,13 @@ describe('request serialisation', () => { }); it('reads a response back into camelCase', async () => { - stubFetch({ port: 5004 }); + stubFetch({ publish_url: 'rtmps://rtmp.example.com:443/key' }); const response = await client().registerInput(COMPOSITION_ID, INPUT_ID, { type: 'rtmp_server', streamKey: 'key', }); - expect(response).toEqual({ port: 5004 }); + expect(response).toEqual({ publishUrl: 'rtmps://rtmp.example.com:443/key' }); }); }); @@ -108,6 +108,22 @@ describe('input variants', () => { await expect(client().registerWhipInput(COMPOSITION_ID, INPUT_ID)).rejects.toThrow(UnknownException); }); + it('returns the RTMP publishing address the server chose', async () => { + stubFetch({ publish_url: 'rtmps://rtmp.example.com:443/k' }); + + await expect(client().registerRtmpInput(COMPOSITION_ID, INPUT_ID, { streamKey: 'k' })).resolves.toBe( + 'rtmps://rtmp.example.com:443/k' + ); + }); + + it('throws when the server returns no RTMP publishing address', async () => { + stubFetch({}); + + await expect(client().registerRtmpInput(COMPOSITION_ID, INPUT_ID, { streamKey: 'k' })).rejects.toThrow( + UnknownException + ); + }); + it('sends the discriminant for each variant', async () => { const cases = [ [(c: CompositionClient) => c.registerWhipInput(COMPOSITION_ID, INPUT_ID), 'whip_server'], @@ -120,7 +136,7 @@ describe('input variants', () => { ] as const; for (const [register, type] of cases) { - const fetch = stubFetch({ bearer_token: 'tok' }); + const fetch = stubFetch({ bearer_token: 'tok', publish_url: 'rtmps://rtmp.example.com:443/k' }); await register(client()); expect(requestBody(fetch).type).toBe(type); vi.unstubAllGlobals();