Feeding a clanker? Grab this page as raw `.md` →

# FramePlayer API


 Bases: `AnyWidget`


Play a sequence of images as an inline, optionally-looping "video".


Hook up any iterable of frames — PIL images, file paths, URLs, bytes, base64 strings, or matplotlib figures (mixed is fine) — and the widget renders the current frame with play/pause/loop controls and a scrubber, so you don't need a second cell that reads a slider value and re-renders.


For long or high-resolution sequences, downsize the frames before passing them in — every frame is base64-inlined into the widget model.



```
from wigglystuff import FramePlayer

import marimo as mo
from PIL import Image
from wigglystuff import FramePlayer

frames = [Image.new("RGB", (128, 128), (i * 8 % 255, 0, 0)) for i in range(30)]
player = mo.ui.anywidget(FramePlayer(frames, interval_ms=80, loop=True))
player
```


Create a FramePlayer.


  Source code in `wigglystuff/frame_player.py`

```
def __init__(
    self,
    frames: Iterable[Any],
    value: int = 0,
    interval_ms: int = 100,
    loop: bool = True,
    width: int = 0,
    show_index: bool = True,
    **kwargs: Any,
) -> None:
    """Create a FramePlayer.

    Args:
        frames: Iterable of frame sources (PIL Image, path, URL, bytes,
            base64 string, or matplotlib figure). Must be non-empty.
        value: Starting frame index.
        interval_ms: Milliseconds between frames while playing.
        loop: Wrap back to the first frame at the end instead of stopping.
        width: Display width in pixels (0 = the image's natural width).
        show_index: Show the "current / total" frame readout.
        **kwargs: Forwarded to ``anywidget.AnyWidget``.
    """
    encoded = self._encode_frames(frames)
    if not encoded:
        raise ValueError("frames must contain at least one frame.")
    if interval_ms <= 0:
        raise ValueError("interval_ms must be positive.")
    value = max(0, min(value, len(encoded) - 1))
    super().__init__(
        frames=encoded,
        value=value,
        interval_ms=interval_ms,
        loop=loop,
        width=width,
        show_index=show_index,
        **kwargs,
    )
```


## n_frames `property`


```
n_frames: int
```


Number of frames currently loaded.


## set_frames


```
set_frames(frames: Iterable[Any]) -> None
```


Replace the frame sequence, re-encoding and clamping `value`.

 Source code in `wigglystuff/frame_player.py`

```
def set_frames(self, frames: Iterable[Any]) -> None:
    """Replace the frame sequence, re-encoding and clamping ``value``."""
    encoded = self._encode_frames(frames)
    if not encoded:
        raise ValueError("frames must contain at least one frame.")
    self.frames = encoded
    if self.value > len(encoded) - 1:
        self.value = len(encoded) - 1
```


## Synced traitlets


| Traitlet | Type | Notes |
| --- | --- | --- |
| `frames` | `list[str]` | Base64 data URIs, one per frame. |
| `value` | `int` | Index of the currently displayed frame. |
| `interval_ms` | `int` | Milliseconds between frames while playing. |
| `playing` | `bool` | Whether playback is currently running. |
| `loop` | `bool` | Wrap back to the first frame at the end instead of stopping. |
| `width` | `int` | Display width in pixels (`0` = the image's natural width). |
| `show_index` | `bool` | Whether to show the "current / total" frame readout. |
