# Control playback
Drive a dotLottie animation from C with a tick loop, control speed, direction and looping, jump to frames, and play segments or named markers.

Playback in the native runtime is driven by you. There is no internal timer — you decide when the animation advances and when it draws, which makes it straightforward to integrate with an existing game loop, render loop, or frame callback.

## The tick loop

`dotlottie_tick()` advances the animation by an elapsed time and redraws only if the visible frame changed. It reports whether it drew, so you can skip presenting a frame identical to the last one.

```c
bool rendered = false;

dotlottie_tick(player, dt, &rendered);

if (rendered) {
  present(buffer, width, height);
}
```

<Callout type="warning" title="dt is in seconds, not milliseconds">
  Pass the time elapsed since your previous tick in **seconds**. A 60 fps frame is `0.0167`, not `16.7`. The example in
  the generated `dotlottie_player.h` derives `dt` from a millisecond clock without converting, which runs the animation
  1000× too fast — if your timer reports milliseconds, divide by 1000.
</Callout>

A complete loop using a monotonic clock:

```c
#include <time.h>

static double now_seconds(void) {
  struct timespec ts;
  clock_gettime(CLOCK_MONOTONIC, &ts);
  return ts.tv_sec + ts.tv_nsec / 1e9;
}

double last = now_seconds();

for (;;) {
  double current = now_seconds();
  float dt = (float)(current - last);
  last = current;

  bool rendered = false;
  dotlottie_tick(player, dt, &rendered);
  if (rendered) {
    present(buffer, width, height);
  }
}
```

Passing a `dt` of `0.0` renders nothing new, and negative values are clamped to zero. Pass `NULL` for `rendered` if you always present regardless.

### Redrawing without advancing

`dotlottie_render()` draws the current frame without moving time forward. Use it after changing something that affects appearance but not position — a theme, a slot, the background, the layout — or to repaint a paused animation after a resize.

```c
dotlottie_set_frame(player, 42.0f);
dotlottie_render(player);
```

## Transport controls

```c
dotlottie_play(player);
dotlottie_pause(player);
dotlottie_stop(player);
```

`dotlottie_pause()` freezes at the current frame; `dotlottie_stop()` also resets to the beginning. Enabling autoplay before loading starts playback automatically:

```c
dotlottie_set_autoplay(player, true);
```

Read the current state with `dotlottie_status()`:

| Status     | Numeric | Meaning                          |
| ---------- | ------- | -------------------------------- |
| `Idle`     | 0       | Created, nothing loaded yet      |
| `Playing`  | 1       | Advancing on each tick           |
| `Paused`   | 2       | Holding the current frame        |
| `Stopped`  | 3       | Reset to the start               |
| `Tweening` | 4       | Interpolating between two frames |

## Speed and direction

```c
dotlottie_set_speed(player, 2.0f);      /* double speed */
dotlottie_set_mode(player, Reverse);
```

| Mode            | Numeric | Behavior                          |
| --------------- | ------- | --------------------------------- |
| `Forward`       | 0       | Start to end                      |
| `Reverse`       | 1       | End to start                      |
| `Bounce`        | 2       | Forward, then back, repeating     |
| `ReverseBounce` | 3       | Backward, then forward, repeating |

Speed is a plain multiplier: `0.5` is half speed, `2.0` is double. A speed of `0.0` stops the animation advancing while leaving it in the `Playing` state.

## Looping

```c
dotlottie_set_loop(player, true);
dotlottie_set_loop_count(player, 3);    /* 0 means loop forever */
```

`dotlottie_set_loop_count()` only applies when looping is enabled. Track progress with:

```c
uint32_t completed = 0;
dotlottie_get_current_loop_count(player, &completed);

bool finished = dotlottie_is_complete(player);
```

With infinite looping, `dotlottie_is_complete()` never returns `true`.

## Frames and timing

```c
float total = 0.0f, duration = 0.0f, current = 0.0f;

dotlottie_get_total_frames(player, &total);
dotlottie_get_duration(player, &duration);     /* seconds */
dotlottie_get_current_frame(player, &current);
```

Jump directly to a frame — fractional values are allowed, which is what makes scrubbing smooth:

```c
dotlottie_set_frame(player, total * 0.75f);
dotlottie_render(player);
```

Frames outside the playable range are rejected with `InvalidParameter`.

### Frame interpolation

By default the player renders at sub-frame positions, producing smoother motion at high refresh rates. Turning it off snaps to whole frames and reduces rendering work:

```c
dotlottie_set_use_frame_interpolation(player, false);
```

## Segments

A segment restricts playback to a frame range. Pass a two-element array of `[start, end]`:

```c
float segment[2] = { 30.0f, 90.0f };
dotlottie_set_segment(player, &segment);
```

Clear it by passing `NULL` to return to the full animation:

```c
dotlottie_set_segment(player, NULL);
```

Read the active segment back into an array you own:

```c
float current_segment[2];
if (dotlottie_get_segment(player, &current_segment) == DOTLOTTIE_SUCCESS) {
  printf("playing %.0f to %.0f\n", current_segment[0], current_segment[1]);
}
```

This returns `InvalidParameter` when no segment is set.

## Markers

Markers are named frame ranges defined in the animation, and they are usually a better choice than hardcoded frame numbers — the designer can move them without you changing code.

```c
dotlottie_set_marker(player, "walk-cycle");
```

Setting a marker applies its range as the current segment. Pass `NULL` to clear it.

Enumerate the markers a file defines:

```c
uint32_t count = 0;
dotlottie_get_markers_count(player, &count);

for (uint32_t i = 0; i < count; i++) {
  const char *name = NULL;
  float start = 0.0f, end = 0.0f;

  if (dotlottie_get_marker(player, i, &name, &start, &end) == DOTLOTTIE_SUCCESS) {
    printf("%s: %.0f to %.0f\n", name, start, end);
  }
}
```

`name` points into memory the library owns — do not free it. Pass `NULL` for `start` or `end` if you only want the name.

To read the marker currently in effect, use the two-call string pattern:

```c
size_t size = 0;
if (dotlottie_get_active_marker(player, NULL, &size) == DOTLOTTIE_SUCCESS) {
  char *active = malloc(size);
  dotlottie_get_active_marker(player, active, NULL);
  /* ... */
  free(active);
}
```

## Background color

```c
dotlottie_set_background(player, 255, 255, 255, 255);   /* opaque white */
dotlottie_set_background(player, 0, 0, 0, 0);           /* transparent */
```

Channels are 8-bit `0`–`255`. Note that colors in the [slot API](/en/runtimes/distributions/native/v0.x/theming-and-slots) use floats from `0.0` to `1.0` instead.

## Audio

For animations with embedded audio, built with the `audio` feature:

```c
dotlottie_set_audio_volume(player, 0.5f);   /* 0.0 silent to 1.0 full */
```

Both audio functions return `FeatureNotEnabled` in a build without the feature. On Android, call `dotlottie_init_android()` with your `JavaVM` and context once before loading an animation with audio.

## Learn more

- [Handle events](/en/runtimes/distributions/native/v0.x/events) — react to loops completing and frames rendering
- [Apply themes and slots](/en/runtimes/distributions/native/v0.x/theming-and-slots) — change appearance at runtime
- [C API reference](/en/runtimes/distributions/native/v0.x/api-reference) — full signatures
