Skip to content

Commit

Permalink
api: Livestream recording customization (#2196)
Browse files Browse the repository at this point in the history
* api/schema: Add recordingSpec field to stream and session

* api/stream: Propagate recordingSpec on child stream and session

* api/webhook: Send recording profiles on upload task

* api/stream: Fix build

I love that ts trick I made

* api/stream: Add test for recordingSpec propagation

* api/webhooks: Add tests for recording.waiting processing
  • Loading branch information
victorges authored Jun 6, 2024
1 parent e8023cd commit c1c3546
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 37 deletions.
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"go-livepeer:broadcaster": "bin/livepeer -broadcaster -datadir ./bin/broadcaster -orchAddr 127.0.0.1:3086 -rtmpAddr 0.0.0.0:3035 -httpAddr :3085 -cliAddr :3075 -v 6 -authWebhookUrl http://127.0.0.1:3004/api/stream/hook -orchWebhookUrl http://127.0.0.1:3004/api/orchestrator",
"go-livepeer:orchestrator": "bin/livepeer -orchestrator -datadir ./bin/orchestrator -transcoder -serviceAddr 127.0.0.1:3086 -cliAddr :3076 -v 6",
"test": "POSTGRES_CONNECT_TIMEOUT=120000 jest -i --silent \"${PWD}/src\"",
"test-single": "POSTGRES_CONNECT_TIMEOUT=120000 jest -i --silent \"${PWD}/src/controllers/+$filename.+\"",
"test-single": "POSTGRES_CONNECT_TIMEOUT=120000 jest -i --silent \"${PWD}/src/.*$filename.*\"",
"test:dev": "jest \"${PWD}/src\" -i --silent --watch",
"test:build": "parcel build --no-autoinstall --no-minify --bundle-node-modules -t browser --out-dir ../dist-worker ../src/worker.js",
"coverage": "yarn run test --coverage",
Expand Down
42 changes: 42 additions & 0 deletions packages/api/src/controllers/stream.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2350,6 +2350,48 @@ describe("controllers/stream", () => {
"http://example-public/playback_id/output.mp4"
);
});

it("should propagate stream configs to child stream and session", async () => {
// create parent stream
const configs = {
record: true,
recordingSpec: {
profiles: [
{
name: "720p",
bitrate: 2000000,
fps: 30,
width: 1280,
height: 720,
},
],
},
};
let res = await client.post(`/stream`, {
...smallStream,
...configs,
});
expect(res.status).toBe(201);
const parent = await res.json();
expect(parent).toMatchObject(configs);

// call transcoding hook
const sessionId = uuid();
res = await client.post(
`/stream/${parent.id}/stream?sessionId=${sessionId}`,
{
name: "session1",
}
);
expect(res.status).toBe(201);
const childStream = await res.json();
expect(childStream.parentId).toEqual(parent.id);
expect(childStream.sessionId).toEqual(sessionId);
expect(childStream).toMatchObject(configs);

const session = await db.session.get(sessionId);
expect(session).toMatchObject(configs);
});
});
});

Expand Down
49 changes: 29 additions & 20 deletions packages/api/src/controllers/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import logger from "../logger";
import {
authorizer,
geolocateMiddleware,
validatePost,
hasAccessToResource,
validatePost,
} from "../middleware";
import { CliArgs } from "../parse-cli";
import {
Expand All @@ -20,9 +20,9 @@ import {
StreamPatchPayload,
StreamSetActivePayload,
User,
Project,
} from "../schema/types";
import { db, jobsDb } from "../store";
import { cache } from "../store/cache";
import { DB } from "../store/db";
import {
BadRequestError,
Expand Down Expand Up @@ -60,7 +60,6 @@ import {
} from "./helpers";
import { toExternalSession } from "./session";
import wowzaHydrate from "./wowza-hydrate";
import { cache } from "../store/cache";

type Profile = DBStream["profiles"][number];
type MultistreamOptions = DBStream["multistream"];
Expand Down Expand Up @@ -91,6 +90,7 @@ const EMPTY_NEW_STREAM_PAYLOAD: Required<
multistream: undefined,
pull: undefined,
record: undefined,
recordingSpec: undefined,
userTags: undefined,
creatorId: undefined,
playbackPolicy: undefined,
Expand Down Expand Up @@ -922,31 +922,39 @@ app.post(
const id = stream.playbackId.slice(0, 4) + uuid().slice(4);
const createdAt = Date.now();

const record = stream.record;
const recordObjectStoreId =
stream.recordObjectStoreId ||
(record ? req.config.recordObjectStoreId : undefined);
const {
id: parentId,
playbackId,
userId,
projectId,
objectStoreId,
record,
recordingSpec,
recordObjectStoreId = record ? req.config.recordObjectStoreId : undefined,
} = stream;
const profiles = hackMistSettings(
req,
useParentProfiles ? stream.profiles : req.body.profiles
);
const childStream: DBStream = wowzaHydrate({
...req.body,
kind: "stream",
userId: stream.userId,
projectId: stream.projectId,
userId,
projectId,
renditions: {},
objectStoreId: stream.objectStoreId,
profiles,
objectStoreId,
record,
recordingSpec,
recordObjectStoreId,
sessionId,
id,
createdAt,
parentId: stream.id,
parentId,
region,
lastSeen: 0,
isActive: true,
});
childStream.profiles = hackMistSettings(
req,
useParentProfiles ? stream.profiles : childStream.profiles
);

const existingSession = await db.session.get(sessionId);
if (existingSession) {
Expand All @@ -956,10 +964,10 @@ app.post(
} else {
const session: DBSession = {
id: sessionId,
parentId: stream.id,
playbackId: stream.playbackId,
userId: stream.userId,
projectId: stream.projectId,
parentId,
playbackId,
userId,
projectId,
kind: "session",
version: "v2",
name: req.body.name,
Expand All @@ -974,8 +982,9 @@ app.post(
ingestRate: 0,
outgoingRate: 0,
deleted: false,
profiles: childStream.profiles,
profiles,
record,
recordingSpec,
recordObjectStoreId,
recordingStatus: record ? "waiting" : undefined,
};
Expand Down
19 changes: 19 additions & 0 deletions packages/api/src/schema/api-schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,21 @@ components:
customization, create and configure an object store.
type: boolean
example: false
recordingSpec:
type: object
description: |
Configuration for recording the stream. This can only be set if
`record` is true.
additionalProperties: false
properties:
profiles:
type: array
items:
$ref: "#/components/schemas/ffmpeg-profile"
description: |
Profiles to record the stream in. If not specified, the stream
will be recorded in the same profiles as the stream itself. Keep
in mind that the source rendition will always be recorded.
multistream:
type: object
additionalProperties: false
Expand Down Expand Up @@ -690,6 +705,8 @@ components:
$ref: "#/components/schemas/ffmpeg-profile"
record:
$ref: "#/components/schemas/stream/properties/record"
recordingSpec:
$ref: "#/components/schemas/stream/properties/recordingSpec"
multistream:
$ref: "#/components/schemas/stream/properties/multistream"
userTags:
Expand Down Expand Up @@ -923,6 +940,8 @@ components:
type: array
items:
$ref: "#/components/schemas/ffmpeg-profile"
recordingSpec:
$ref: "#/components/schemas/stream/properties/recordingSpec"
error:
type: object
properties:
Expand Down
5 changes: 3 additions & 2 deletions packages/api/src/test-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
* This file is imported from all the integration tests. It boots up a server based on the provided argv.
*/
import fs from "fs-extra";
import { v4 as uuid } from "uuid";
import path from "path";
import os from "os";
import path from "path";
import { v4 as uuid } from "uuid";

import makeApp, { AppServer } from "./index";
import argParser from "./parse-cli";
Expand Down Expand Up @@ -38,6 +38,7 @@ params.sendgridTemplateId = sendgridTemplateId;
params.sendgridApiKey = sendgridApiKey;
params.postgresUrl = `postgresql://[email protected]/${testId}`;
params.recordObjectStoreId = "mock_store";
params.recordCatalystObjectStoreId = "mock_store";
params.vodObjectStoreId = "mock_vod_store";
params.trustedIpfsGateways = [
"https://ipfs.example.com/ipfs/",
Expand Down
Loading

0 comments on commit c1c3546

Please sign in to comment.