# WrappedPlayer

`WrappedPlayer` is the video. It mounts every scene on one stage, builds a single GSAP timeline from them with a transition between each pair, and puts a transport under it: play, replay, the timeline, a clock and full screen. Use it whenever you want a video on a page without the rest of `WrappedView`.

> Live preview: try it at https://bettertyped.github.io/react-wrapped/docs/player/wrapped-player/
>
> Tap the left or right third of the frame to step between scenes.

## Installation

```bash
pnpm add @react-wrapped/player @react-wrapped/core gsap
```

```tsx
import "@react-wrapped/core/styles.css";
import "@react-wrapped/player/styles.css";
```

## Usage

Give it who the video is about, a seed, the scenes and a config. The component below is the preview above: the player in a box with a height, and five buttons that drive it through its ref.

`player-remote.tsx`

```tsx
import { type CSSProperties, useRef, useState } from "react";

import type { SceneInstance, WrappedConfig, WrappedSubject } from "@react-wrapped/core";
import { WrappedPlayer, type WrappedPlayerHandle } from "@react-wrapped/player";

// The player fits its frame into the room it is given, so the room needs a height.
const ROOM: CSSProperties = { display: "grid", height: 560 };

export function PlayerRemote({
  subject,
  seed,
  scenes,
  config,
}: {
  subject: WrappedSubject;
  seed: string;
  scenes: readonly SceneInstance[];
  config: WrappedConfig;
}) {
  const player = useRef<WrappedPlayerHandle>(null);
  const [said, setSaid] = useState("Nothing measured yet.");

  const measure = () => {
    const handle = player.current;
    if (!handle) return;
    // Both are in seconds at normal speed: the length of the video, and where each scene starts in it.
    const starts = handle.scenes().map((scene) => `${scene.label} at ${scene.time.toFixed(1)}s`);
    setSaid(`${handle.duration().toFixed(1)}s in all. ${starts.join(", ")}.`);
  };

  return (
    <div>
      <div style={ROOM}>
        <WrappedPlayer ref={player} subject={subject} seed={seed} scenes={scenes} config={config} />
      </div>
      <div role="group" aria-label="Drive the player from outside">
        <button type="button" onClick={() => player.current?.play()}>
          Play
        </button>
        <button type="button" onClick={() => player.current?.pause()}>
          Pause
        </button>
        <button type="button" onClick={() => player.current?.restart()}>
          Restart
        </button>
        <button type="button" onClick={() => player.current?.seek(player.current.duration())}>
          Jump to the end
        </button>
        <button type="button" onClick={measure}>
          Measure
        </button>
      </div>
      <output>{said}</output>
    </div>
  );
}
```

## Give it a height

The frame is always drawn at its real size and scaled to fit the room the player has: the width of its container and the height left above the transport. A container with no height gives it a room of 320 pixels, which makes a small 9:16 frame. Put the player in a box with a height, or in a flex column and let it grow. On a phone the frame never takes more than 72 percent of the screen's height, so the controls stay in view.

## Props

| Prop | Type | What it does |
| --- | --- | --- |
| `ref?` | `Ref<WrappedPlayerHandle>` | Hands out the player's handle, to drive it from your own code. |
| `subject` | `WrappedSubject` | Who the video is about. Shown in the corner of every frame. |
| `seed` | `string` | Picks the transitions and the small variations in motion. The same seed always plays the same video. |
| `scenes` | `readonly SceneInstance[]` | The scenes that play, in order. Keep the array the same between renders: a new one rebuilds the timeline. |
| `config` | `WrappedConfig` | Palette, format, cell shape, speed, grain and picked transitions. |
| `autoplay?` | `boolean` | Starts playing as soon as the video is built. On unless you say otherwise. |
| `editing?` | `TimelineEditing & { scenes: readonly SceneInstance[] }` | Turns the timeline into the editor. Leave it out for someone who only watches. |
| `className?` | `string` | A class for the player's root, for sizing it in your layout. |
| `classNames?` | `SlotClassNames<WrappedPlayerSlot \| TimelineSlot>` | A class per part, by slot name: `"player-play"`, `"timeline-segment-bar"`. See [styling](https://bettertyped.github.io/react-wrapped/docs/guides/styling/). |

## The subject

| Key | Type | What it is |
| --- | --- | --- |
| `handle` | `string` | Shown as @handle in the frame's top corner. |
| `name?` | `string \| null` | The person's display name. The frame shows the handle. This one is for your own page. |
| `avatarUrl?` | `string \| null` | Their picture. Left out, the first two letters of the handle on a disc. |
| `caption` | `string` | The small line under the handle: what this Wrapped is called. |
| `period?` | `string` | The window the numbers cover, already formatted. The frame does not draw it: write it into a scene, such as the opening's line. |
| `mark?` | `string \| readonly string[]` | A logo pressed into the bottom right corner, as SVG path data. See [marks](https://bettertyped.github.io/react-wrapped/docs/marks/). |
| `brand?` | `string` | A quiet line in the bottom left: a site address or a product name. |

## The handle

`ref` gives you a `WrappedPlayerHandle`. Every player has its own, so any number of them can share a page. All times are seconds of the video at normal speed, whatever speed the config sets.

| Method | Type | What it does |
| --- | --- | --- |
| `play` | `() => void` | Plays from where the playhead is. At the end of the video it starts over. |
| `pause` | `() => void` | Stops where it is. |
| `restart` | `() => void` | Plays from the first frame. |
| `seek` | `(seconds: number) => void` | Jumps to a moment, in seconds at normal speed, and stays there paused. |
| `duration` | `() => number` | The length of the video in seconds at normal speed. Zero while there is no video. |
| `scenes` | `() => readonly SceneMark[]` | Each playing scene with its `key`, its `label` and the `time` it starts at, in play order. |
| `take` | `() => StageTake \| null` | Lends out the stage and the playhead for making a file. See [video export](https://bettertyped.github.io/react-wrapped/docs/player/video-export/). |

## Controls

- **The frame.** A tap on the left third goes back to the start of the scene, or to the one before right after a cut. The right third goes on to the next scene. The middle pauses and plays.
- **The buttons.** Play or pause, play from the start, full screen. They are the accessible controls. The frame taps are a shortcut for touch.
- **The keyboard.** With focus in the transport, the left and right arrows step between scenes and `f` toggles full screen.
- **Full screen** plays in a dark room, whatever the page's theme is.

## When the video is rebuilt

The timeline is rebuilt when the `scenes` array, the format, the seed or `config.cuts` changes. The playhead stays where it was, and a scene whose content did not change keeps the animation it already built, so a scene joining mid-play costs almost nothing. Speed changes without a rebuild. This is why the scenes array and the config should come from state or `useMemo`: a fresh array on every render rebuilds the video on every render.

## Related

- [Player](https://bettertyped.github.io/react-wrapped/docs/player/)
- [Timeline](https://bettertyped.github.io/react-wrapped/docs/player/timeline/)
- [Video export](https://bettertyped.github.io/react-wrapped/docs/player/video-export/)
- [Scenes](https://bettertyped.github.io/react-wrapped/docs/scenes/)
- [Build the page from parts](https://bettertyped.github.io/react-wrapped/docs/guides/from-parts/)

Source: https://bettertyped.github.io/react-wrapped/docs/player/wrapped-player/
