> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Live Events

> Managing live event streams on Livepeer: multistream targets, recording, access control, and stream lifecycle operations.

export const CenteredContainer = ({children, maxWidth = "800px", padding = "0", preset = "default", width = "", minWidth = "", marginRight = "", marginBottom = "", textAlign = "", style = {}, className = "", ...rest}) => {
  const presets = {
    default: {},
    fitContent: {
      width: "fit-content",
      minWidth: "fit-content"
    },
    readable70: {
      width: "70%",
      minWidth: "fit-content"
    },
    readable80: {
      width: "80%",
      minWidth: "fit-content"
    },
    readable90: {
      width: "90%"
    },
    wide900: {
      maxWidth: "900px"
    }
  };
  const presetStyle = presets[preset] || presets.default;
  return <div className={className} style={{
    maxWidth: presetStyle.maxWidth || maxWidth,
    margin: "0 auto",
    padding: padding,
    ...presetStyle.width ? {
      width: presetStyle.width
    } : {},
    ...presetStyle.minWidth ? {
      minWidth: presetStyle.minWidth
    } : {},
    ...width ? {
      width
    } : {},
    ...minWidth ? {
      minWidth
    } : {},
    ...marginRight ? {
      marginRight
    } : {},
    ...marginBottom ? {
      marginBottom
    } : {},
    ...textAlign ? {
      textAlign
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

export const LinkArrow = ({href, label, description, newline = true, borderColor, className = '', style = {}, ...rest}) => {
  const linkArrowStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: "var(--lp-spacing-1)",
    width: 'fit-content',
    ...borderColor && ({
      borderColor
    })
  };
  return <span className={className} style={style} {...rest}>
      {newline && <br />}
      <span style={linkArrowStyle}>
        <a href={href} target="_blank" rel="noopener noreferrer">
          {label}
        </a>
        <Icon icon="arrow-up-right" size={14} color="var(--lp-color-accent)" />
      </span>
      {description && description}
      {description && <div style={{
    height: "var(--lp-spacing-3)"
  }} />}
    </span>;
};

<CenteredContainer preset="readable90">
  <Tip>Livepeer streams support simultaneous multistream destinations, automatic recording, and token-gated playback through playback policies, all configurable at stream creation or via update.</Tip>
</CenteredContainer>

***

A Livepeer live event stream supports three features layered on top of basic RTMP ingest: simultaneous restreaming to third-party RTMP destinations (multistream), automatic recording to a VOD asset, and playback access control via JWT tokens or webhook verification.

<CustomDivider />

## Multistream Targets

Multistream simultaneously pushes a live stream to additional RTMP destinations (YouTube Live, Twitch, custom RTMP servers) alongside Livepeer transcoding.

Create a multistream target:

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
import { Livepeer } from 'livepeer';
const client = new Livepeer({ apiKey: process.env.LIVEPEER_API_KEY });

const target = await client.multistream.createTarget({
  url: 'rtmp://a.rtmp.youtube.com/live2/<youtube-stream-key>',
  name: 'YouTube Live',
  disabled: false,
});
```

Attach the target to a stream at creation or via update:

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
const { stream } = await client.stream.create({
  name: 'my-event',
  profiles: [...],
  multistream: {
    targets: [
      { id: target.multistreamTarget.id, profile: '720p' },
    ],
  },
});
```

The `profile` field specifies which transcoding rendition to forward. Omitting it sends the original ingest quality.

<CustomDivider />

## Recording

Set `record: true` on stream creation to automatically archive the stream to a VOD asset:

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
const { stream } = await client.stream.create({
  name: 'recorded-event',
  profiles: [...],
  record: true,
  recordingSpec: {
    profiles: [
      { name: '720p', bitrate: 3000000, fps: 30, width: 1280, height: 720 },
    ],
  },
});
```

When the stream goes idle, Livepeer generates a VOD asset from the recording. The `stream.recording.ready` webhook fires when the asset is available. Retrieve the recording:

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
const recordings = await client.stream.getAll({ streamId: stream.id });
```

<CustomDivider />

## Playback Access Control

By default, streams are publicly playable. Restrict playback using a playback policy:

**JWT-based access control:**

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
const signingKey = await client.accessControl.createSigningKey();

const { stream } = await client.stream.create({
  name: 'private-event',
  profiles: [...],
  playbackPolicy: {
    type: 'jwt',
    webhookContext: {},
  },
});
```

Livepeer validates a signed JWT in the `accessKey` query parameter of every playback request. Your application issues tokens signed with the signing key private key.

**Webhook-based access control:**

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
const { stream } = await client.stream.create({
  name: 'webhook-gated',
  profiles: [...],
  playbackPolicy: {
    type: 'webhook',
    webhookId: '<your-webhook-id>',
    webhookContext: { eventId: 'event-2026' },
  },
});
```

For each playback request, Livepeer calls your webhook URL with the request context. Your application returns 200 to allow or 4xx to deny.

<CustomDivider />

## Updating and Terminating Streams

Update a stream's configuration while it is active:

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
await client.stream.update(stream.id, {
  record: true,
  multistream: { targets: [...] },
});
```

Terminate an active stream (force-disconnects the RTMP publisher):

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
await client.stream.terminate(stream.id);
```

Retrieve stream status:

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
const { stream: current } = await client.stream.get(stream.id);
console.log(current.isActive);    // true while RTMP is connected
console.log(current.lastSeen);    // epoch ms of last frame received
```

<CustomDivider />

## Related Pages

<CardGroup cols={2}>
  <Card title="Ingest" icon="broadcast" href="/v2/developers/build/video/ingest-and-playback" arrow horizontal>
    RTMP ingest configuration and stream creation.
  </Card>

  <Card title="VOD" icon="film" href="/v2/developers/build/video/vod-and-recording" arrow horizontal>
    Working with recorded assets after a stream ends.
  </Card>

  <Card title="Player" icon="play" href="/v2/developers/build/applications/frontend-react-player" arrow horizontal>
    @livepeer/react Player for HLS and low-latency WebRTC playback.
  </Card>

  <Card title="Video Overview" icon="grid" href="/v2/developers/build/video/overview" arrow horizontal>
    Access paths and workload types for Livepeer video.
  </Card>
</CardGroup>
