Self-hosted multi-platform broadcast newsroom for activist collectives: publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style column wall with hostile-content-safe rendering, live control room with Auto Director, bridged chat governance with polls and quorum, distributed sandboxed transcode workers, encrypted content-addressed archive, monthly analytics. MIT licensed. See README.md and docs/.
58 lines
2.7 KiB
TypeScript
58 lines
2.7 KiB
TypeScript
import { Module } from "@nestjs/common";
|
|
import { withTenant } from "../db/client.js";
|
|
import { LiveDestinationController } from "./live-destination.controller.js";
|
|
import { LiveDestinationRepository, LIVE_DESTINATION_DEPENDENCIES, type TenantTransaction } from "./live-destination.repository.js";
|
|
import { GoLiveService, GO_LIVE_DEPENDENCIES } from "./go-live.service.js";
|
|
import { StreamOutputController } from "./stream-output.controller.js";
|
|
import { createPublishingRegistry } from "@tactical-media-broadcast-hub/connector-runtime";
|
|
import { pool } from "../db/client.js";
|
|
|
|
/** Registers connector-level live source resolvers from the connector registry. */
|
|
function registerSourceResolvers(goLive: GoLiveService): void {
|
|
// Registry pattern: the provider set comes from the publishing registry —
|
|
// connector packages (twitch et al.) own provider behaviour, so every
|
|
// connector implementing resolveLiveSource registers under its own manifest
|
|
// id; generic-hls resolves through the built-in prober. No provider names
|
|
// branch here.
|
|
if (process.env.DATABASE_URL === undefined) return;
|
|
try {
|
|
const registry = createPublishingRegistry(pool);
|
|
for (const connector of registry.list()) {
|
|
const { id } = connector.manifest();
|
|
if (connector.resolveLiveSource === undefined) continue;
|
|
const resolveLiveSource = connector.resolveLiveSource.bind(connector);
|
|
goLive.registerSourceResolver(id, async (request) => resolveLiveSource(
|
|
{
|
|
id: `${id}:live-source`, workspaceId: "live-source", provider: id,
|
|
channelId: request.channelId ?? request.externalUrl ?? "",
|
|
credentialId: { kind: "credential-ref", id: `${id}:live-source` },
|
|
fields: {}, createdAt: new Date(),
|
|
},
|
|
request,
|
|
));
|
|
}
|
|
} catch { /* No connectors configured in this process: only generic-hls stays registered. */ }
|
|
}
|
|
|
|
@Module({
|
|
controllers: [LiveDestinationController, StreamOutputController],
|
|
providers: [
|
|
{ provide: "STREAM_OUTPUT_TRANSACTION", useValue: withTenant satisfies TenantTransaction },
|
|
{
|
|
provide: LiveDestinationRepository,
|
|
useFactory: (dependencies?: unknown) => new LiveDestinationRepository(dependencies as never),
|
|
inject: [{ token: LIVE_DESTINATION_DEPENDENCIES, optional: true }],
|
|
},
|
|
{
|
|
provide: GoLiveService,
|
|
useFactory: (destinations: LiveDestinationRepository, dependencies?: unknown) => {
|
|
const service = new GoLiveService(destinations, dependencies as never);
|
|
registerSourceResolvers(service);
|
|
return service;
|
|
},
|
|
inject: [LiveDestinationRepository, { token: GO_LIVE_DEPENDENCIES, optional: true }],
|
|
},
|
|
],
|
|
exports: [LiveDestinationRepository, GoLiveService],
|
|
})
|
|
export class LiveModule {}
|