Integration
Consume trim capability in a screen by adapting a concrete player (SDK or HTML video) to VideoTrimPlayerContract, then mounting manager-backed UI only when player duration is valid.
Current reference implementation in this repo:
src/modules/content/details/content-tools/video-trim/index.tsxsrc/hooks/video-trim/useTrimPlayerAdapter.tssrc/hooks/video-trim/useVideoTrimManager.tssrc/modules/video-trim/index.tsx
Integration Flow
Entitlement/content API
↓
Resolve playable source + auth context
↓
Initialize player SDK instance
↓
Wait for metadata/duration readiness (finite and > 0)
↓
Adapt SDK player to VideoTrimPlayerContract
↓
useVideoTrimManager
↓
useTrimSelection + useTrimSubmission
↓
Video Trim Module UI
Practical Rule
⚠️ Warning
The manager throws when duration is not a positive finite number.Do not initialize trim manager while duration is
0,NaN, orInfinity.
Use either approach:
- keep player as
nulluntil duration is ready - keep module unmounted until duration is ready (current reference approach)
SDK Consumption Example
The example below mirrors the intended shape when integrating a real player SDK. It is intentionally simplified and is not a copy-paste production implementation.
:::tip Reference Notice
This document shows reference patterns, not exact production implementation code.
Use it to understand integration boundaries and sequencing, then align concrete code with:
- current workspace implementation
- player SDK API details
- team conventions and environment constraints
:::
type EntitlementPayload = {
playbackUrl: string;
token: string;
};
type SdkPlayer = {
play: () => void;
pause: () => void;
seek: (seconds: number) => void;
getCurrentTime: () => number;
getDuration: () => number;
on: (event: 'timeupdate' | 'loadedmetadata' | 'durationchange', cb: () => void) => void;
off: (event: 'timeupdate' | 'loadedmetadata' | 'durationchange', cb: () => void) => void;
destroy: () => void;
};
function VideoTrimSdkTool({ entitlement }: { entitlement: EntitlementPayload | null }) {
const [sdkPlayer, setSdkPlayer] = useState<SdkPlayer | null>(null);
const [isDurationReady, setIsDurationReady] = useState(false);
useEffect(() => {
if (!entitlement?.playbackUrl || !entitlement.token) {
setSdkPlayer(null);
setIsDurationReady(false);
return;
}
const instance = createVendorPlayer({
source: entitlement.playbackUrl,
token: entitlement.token,
});
const syncDurationReadiness = () => {
const durationSeconds = instance.getDuration();
setIsDurationReady(Number.isFinite(durationSeconds) && durationSeconds > 0);
};
instance.on('loadedmetadata', syncDurationReadiness);
instance.on('durationchange', syncDurationReadiness);
syncDurationReadiness();
setSdkPlayer(instance);
return () => {
instance.off('loadedmetadata', syncDurationReadiness);
instance.off('durationchange', syncDurationReadiness);
instance.destroy();
setSdkPlayer(null);
setIsDurationReady(false);
};
}, [entitlement]);
const player = useMemo(() => {
if (!sdkPlayer) {
return null;
}
return {
play: () => sdkPlayer.play(),
pause: () => sdkPlayer.pause(),
seek: (timeSeconds: number) => sdkPlayer.seek(Math.max(0, timeSeconds)),
getCurrentTime: () => sdkPlayer.getCurrentTime(),
getDuration: () => sdkPlayer.getDuration(),
subscribeToTimeUpdates: (callback: (timeSeconds: number) => void) => {
const onTimeUpdate = () => {
callback(sdkPlayer.getCurrentTime());
};
sdkPlayer.on('timeupdate', onTimeUpdate);
return () => {
sdkPlayer.off('timeupdate', onTimeUpdate);
};
},
};
}, [sdkPlayer]);
return (
<>
{!isDurationReady ? <p>Loading player metadata...</p> : null}
{isDurationReady ? <VideoTrimModule player={player} /> : null}
</>
);
}
Integration Rules
- Manager remains source of truth.
- UI reads state via hooks; UI does not duplicate manager state.
- Adapter owns SDK/event translation and unsubscribe cleanup.
- Entitlement/loading flows are caller-owned, not manager-owned.
data-testidstays stable for behavior-driven tests.- Do not introduce Context in v1.1 unless multiple unrelated trees require shared access.