Integration

Prev Next

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.tsx
  • src/hooks/video-trim/useTrimPlayerAdapter.ts
  • src/hooks/video-trim/useVideoTrimManager.ts
  • src/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, or Infinity.

Use either approach:

  • keep player as null until 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-testid stays stable for behavior-driven tests.
  • Do not introduce Context in v1.1 unless multiple unrelated trees require shared access.