Verify a live stream
StreamSdk validates each segment of an HLS or DASH stream as it lands, so the verified state stays current throughout playback.
StreamSdk.create(ledger: VerifierLedger, options: { trustAnchors: string[]; trustC2paAnchors?: boolean }): StreamSdk
options.trustAnchors are PEM trust anchors (see Trust anchors). ledger is a caller-provided cache of per-stream state (see Stream ledger).
The lifecycle
A stream lives between two events: the first call to validate(streamId, ...) for that id, and a final call to stop(streamId) when playback ends. In between, call validate once per segment, in arrival order, with the same streamId.
import { StreamSdk, type VerifierLedger } from '@limboai/verifox-sdk-verifier';
const sdk = StreamSdk.create(ledger, { trustAnchors: [orgCaPem] });
const streamId = 'home-page-live';
async function onSegmentArrived(bytes: Uint8Array) {
const result = await sdk.validate(streamId, bytes);
switch (result.kind) {
case 'init':
// First segment of a session: the signed init manifest.
updateBadge(result.state);
break;
case 'media':
// Each subsequent media segment.
if (!result.valid) markTampered(result.codes);
break;
case 'unknown':
// Not a recognizable C2PA stream segment; ignore.
break;
}
}
Use any stable string for streamId (a URL or a player instance id both work). Different ids are validated independently. Calls for the same id must not overlap: the SDK reads and writes per-stream state through your ledger, which must provide atomic per-streamId read-modify-write (see the atomicity rule).
The result union
validate resolves to a SegmentValidation, a discriminated union on kind. Switch on kind and never read fields off the wrong arm.
type SegmentValidation = InitValidation | MediaValidation | UnknownSegment;
interface InitValidation {
kind: 'init';
reader: ManifestStore; // parsed manifest store for the init manifest, from `@contentauth/c2pa-types`
state: ValidationState; // "Invalid" | "Valid" | "Trusted"
codes: ValidationCode[];
sessionKeys: SessionKeySummary[]; // keys this session's media segments may use
}
interface MediaValidation {
kind: 'media';
valid: boolean;
keyId: Uint8Array; // session key that signed this segment
timing: SegmentTiming;
manifestId: string; // init manifest this segment belongs to
codes: ValidationCode[];
}
interface UnknownSegment {
kind: 'unknown';
}
init: the signed init manifest opening a session.statedrives the badge (see Validation states);sessionKeysare cached for the media segments that follow.media: a media segment.validis the headline;codescarry the detail when it isfalse(see Failure codes).unknown: bytes that are not a recognizable stream segment. No-op.
SegmentTiming
interface SegmentTiming {
sequenceNumber: number;
timescale?: number;
eventDurationSecs?: number;
}
sequenceNumber orders segments within a session. timescale and eventDurationSecs describe the segment's media duration when the container provides them.
SessionKeySummary
interface SessionKeySummary {
keyId: Uint8Array;
publicKey: Uint8Array;
}
Returned on InitValidation. Each entry is a session key the issuer minted for this session; media segments reference one by keyId. The SDK caches these in the ledger so later media validations resolve their signing key without re-reading the init manifest.
Cleanup
await sdk.stop(streamId);
stop invokes forget(streamId) on the ledger. Call it from the player's unmount or destroy hook.
See also
- Stream ledger: the caller-provided per-stream cache and its atomicity contract.
- Failure codes: interpreting
codesand surfacing them to viewers. - Validation states: what
Invalid/Valid/Trustedmean.