# Livestream chat

Livestream chat sits next to a video stream, with thousands of viewers, a fast flow of short messages and a list that has to stay smooth for hours on a phone. This page covers which channel type to use and how to keep memory and rendering bounded. It also covers unread counts without read events, and which features to turn off. The [Livestream message list](https://getstream.io/chat/docs/sdk/react-native/ui-cookbooks/message-list/livestream-message-list/) UI cookbook builds the screen, with the video above or behind the chat.

## What users get

>
> **Info:** **Example**
>
> The user opens a concert stream. The video plays above the chat. The chat shows the newest messages and follows them as they arrive, several a second.
>
> The user scrolls up to read a question. New messages keep arriving below, and the scroll-to-bottom button takes them back to the latest one. When they return to the bottom, the list follows the newest messages again.
>
> An hour later, the app still holds only the newest few hundred messages, not every message of the stream.
>

## How it works across the layers

### Server: the livestream channel type

Stream has a built-in `livestream` channel type. Users don't have to be members to read and write. Any user with a valid token can watch the channel and post. Its `url_enrichment` setting is off by default.

Check the type's Read events setting (`read_events`) and turn it off. [Livestream best practices](https://getstream.io/chat/docs/javascript/livestream-best-practices/) recommends turning off read events, typing events and connect events on the type. It also explains the server's own message throttling. [Channel types](https://getstream.io/chat/docs/javascript/channel-features/) lists every setting.

The channel type's settings decide what the client may do. A setting that is off on the server is off in the client too, whatever `client.config` says. See [Configuration](https://getstream.io/chat/docs/sdk/react-native/state/state-and-entities/configuration/).

### State: the message paginator

The channel's messages live in `channel.messagePaginator`. Two of its settings matter for a busy channel:

- `stateThrottleMs` limits how often the paginator publishes its `items`, and with them how often the list renders. It is 500 ms by default, so a burst of messages causes about two renders a second. Messages the user sends show at once.
- `maxLoadedItems` caps how many messages the paginator keeps. It is off by default. See [Capping the messages](#capping-the-messages).

[Throttling and the memory cap](https://getstream.io/chat/docs/sdk/react-native/state/loading-data/message-paginators/#throttling-and-the-memory-cap) explains both.

### State: read state without read events

With `read_events` off, the server keeps no read state for the channel, so nothing counts as unread. [Turning read events off](https://getstream.io/chat/docs/sdk/react-native/state/channels-and-threads/read-state-and-unread-counts/#turning-read-events-off) in Read state and unread counts explains what else changes. [Counting unread messages locally](#counting-unread-messages-locally) shows how to bring back an unread count that lives only on the device.

### UI: the message list

`MessageList` and `MessageFlashList` both take `isLiveStreaming`, which tunes the list for many messages a second. `MessageFlashList` renders with `FlashList` from `@shopify/flash-list`, which reuses the views of rows that scroll out for the rows that scroll in, where `MessageList` creates a view for every message. `MessageFlashList` is experimental, and so is `isLiveStreaming` on both lists. [MessageList and MessageFlashList](https://getstream.io/chat/docs/sdk/react-native/ui/components/message-list-ui/#messagelist-and-messageflashlist) in Message list UI compares them, and [Performance](https://getstream.io/chat/docs/sdk/react-native/features/app-experience/performance/#livestreams) lists what `isLiveStreaming` changes and the `Channel` props that take parts out of every row.

The lists pause the memory cap while the user reads near the oldest loaded message, which [Scrolling back](#scrolling-back) explains.

## Turning it on or off

### Opening the channel

`Channel` watches the channel when it mounts, unless the channel is already watched and loaded with its latest messages, so a livestream screen needs no request of its own. It watches without `presence: true`, which a livestream doesn't need. This screen opens one livestream channel. It sits inside `OverlayProvider` and `Chat`:

```tsx title="src/LivestreamScreen.tsx"
import { useMemo } from "react";
import {
  Channel,
  MessageComposer,
  MessageFlashList,
  useChatContext,
} from "stream-chat-react-native";

export const LivestreamScreen = () => {
  const { client } = useChatContext();
  const channel = useMemo(
    () => client.channel("livestream", "concert"),
    [client],
  );

  return (
    <Channel channel={channel}>
      <MessageFlashList isLiveStreaming />
      <MessageComposer />
    </Channel>
  );
};
```

`Channel` sets the page size of the message list to 25 messages, unless you registered one in `client.config`. Later pages load as the user scrolls up.

### Capping the messages

Set `maxLoadedItems` on the message paginators. Once the newest messages loaded exceed the cap, each new message drops the oldest one. To cap every channel, set it in `client.config`. Name `pageSize` with it, because a change to these keys drops the page size `Channel` sets:

```ts
import type { StreamChat } from "stream-chat";

export const capEveryChannel = (client: StreamChat) => {
  client.config.set({
    channel: { messagePaginator: { maxLoadedItems: 500, pageSize: 25 } },
  });
};
```

To cap only livestream channels, set it from a setup function. It runs for every channel, so it can check the channel's type:

```ts
import type { StreamChat } from "stream-chat";

export const capLivestreamChannels = (client: StreamChat) => {
  client.config.setSetupFunction("channel", ({ channel }) => {
    if (channel.type !== "livestream") return;
    channel.messagePaginator.updateConfig({ maxLoadedItems: 500 });
  });
};
```

There is one `channel` setup function, so this one replaces any you set before; merge them into one. [Setup functions](https://getstream.io/chat/docs/sdk/react-native/state/state-and-entities/configuration/#setup-functions) in Configuration explains how they run.

[Throttling and the memory cap](https://getstream.io/chat/docs/sdk/react-native/state/loading-data/message-paginators/#throttling-and-the-memory-cap) in Message paginators explains how the cap behaves: which messages it never drops, and when it applies.

#### Scrolling back

>
> **Info:** **Example**
>
> The cap is 500, and a page is 25 messages. The user scrolls back through the 500 loaded messages, and the next page loads, so the paginator holds 525. The next new message would drop the 26 oldest, which are the ones the user is reading. The list jumps.
>

`MessageList` and `MessageFlashList` prevent this. They pause the cap while the user is within a fifth of the cap from the oldest loaded message, here within 100 messages of it. While it's paused, the paginator keeps every message. Once the user is back nearer the newest messages, the next new message drops everything over the cap at once. A list of your own pauses the cap with `MessagePaginator.setPruningSuspended()`, and resumes it when the user returns.

#### Jumping to a message

A jump, for example to a quoted message or a pinned announcement, shows the messages around the target instead of the newest ones. The cap applies to neither the messages around the target nor the newest messages while they're off screen, so the newest messages keep growing as messages arrive. When the user jumps back to the latest message, the next new message brings the newest messages back down to the cap. A channel left after a jump opens on its newest messages again. [Throttling and the memory cap](https://getstream.io/chat/docs/sdk/react-native/state/loading-data/message-paginators/#throttling-and-the-memory-cap) in Message paginators explains when the cap applies.

### Counting unread messages locally

Turn on `channel.readEvents.localUnreadCountEnabled` in `client.config` to count unread messages on this device, without the server. [Turning read events off](https://getstream.io/chat/docs/sdk/react-native/state/channels-and-threads/read-state-and-unread-counts/#turning-read-events-off) in Read state and unread counts explains what the local count counts, how it resets and who sees it:

```ts
import type { StreamChat } from "stream-chat";

export const countUnreadLocally = (client: StreamChat) => {
  client.config.set({
    channel: { readEvents: { localUnreadCountEnabled: true } },
  });
};
```

#### What the user sees

Without the local count, the UI has no unread count for the channel. Its row in a channel list has no unread badge, the scroll-to-bottom button has no number, and the list shows no unread notification.

With the local count:

- The channel's row in a channel list shows the unread count.
- The scroll-to-bottom button shows the number of messages that arrived while the user was scrolled up.
- The list shows the unread notification, and marks the channel read locally, which resets the count and sends nothing, when the user catches up.

In both cases the user can't mark a message unread. That action needs the `read-events` capability, which channels without read events don't give.

### Turning off typing, presence and delivery receipts

Typing events are per channel type. Turn off `typing_events` on the `livestream` type on the server, and the client sends and shows none.

In the client, a `messageComposer` setup function can stop the composers of livestream channels from sending typing events:

```ts
import type { StreamChat } from "stream-chat";

export const noTypingEventsInLivestreams = (client: StreamChat) => {
  client.config.setSetupFunction("messageComposer", ({ composer }) => {
    if (composer.channel.type !== "livestream") return;
    composer.updateConfig({ text: { publishTypingEvents: false } });
  });
};
```

To turn typing off in this client for every channel instead, set `channel.typingEvents.enabled: false` in `client.config`. [Typing indicators](https://getstream.io/chat/docs/sdk/react-native/features/messaging/typing-indicators/#turning-it-on-or-off) explains the server and client settings.

For presence, `Channel` watches the channel without `presence: true`, and you turn off `connect_events` on the channel type.

Delivery receipts are per channel type too. When the type's `delivery_events` setting is on, every viewer reports delivery of the newest message through `client.messageDeliveryReporter`, about once a second while messages arrive. Turn off `delivery_events` on the `livestream` type, or set `channel.deliveryEvents.enabled: false` in `client.config` to stop this client from reporting delivery. [Message delivery and read status](https://getstream.io/chat/docs/javascript/message-delivery-and-read-status/#enabling-delivery-receipts) explains the type setting.

### Offline support

With `enableOfflineSupport` on `Chat`, every incoming message is written to the local database. A livestream list is ephemeral and moves forward only, so the writes cost more than they give, and Stream's [livestream best practices](https://getstream.io/chat/docs/javascript/livestream-best-practices/) recommend turning offline storage off. Offline support is off by default and applies to the whole app. [Offline support](https://getstream.io/chat/docs/sdk/react-native/state/offline-support/offline-support/) explains what it stores.

## Customizing

### A lighter list

These `Channel` props take parts out of every row, at the cost of what those parts show. [Performance](https://getstream.io/chat/docs/sdk/react-native/features/app-experience/performance/#livestreams) explains each:

```tsx
import type { PropsWithChildren } from "react";
import type { Channel as StreamChannel } from "stream-chat";
import { Channel } from "stream-chat-react-native";

export const LightLivestreamChannel = ({
  channel,
  children,
}: PropsWithChildren<{ channel: StreamChannel }>) => (
  <Channel
    channel={channel}
    enableMessageGroupingByUser={false}
    hideDateSeparators
    hideStickyDateHeader
  >
    {children}
  </Channel>
);
```

Keep a custom message UI light too. The list mounts and measures each row as the user scrolls, so a message component with few nested views mounts faster.

### Showing the viewer count

`channel.state` holds `watcherCount`, the number of users watching the channel. The server sends it with the channel query, and events from the channel can carry an update. `useChannelOnlineMemberCount(channel)` returns it and updates with it. This badge shows the count:

```tsx
import { Text } from "react-native";
import {
  useChannel,
  useChannelOnlineMemberCount,
} from "stream-chat-react-native";

export const ViewerCount = () => {
  const channel = useChannel();
  const watcherCount = useChannelOnlineMemberCount(channel);

  return <Text>{watcherCount} watching</Text>;
};
```

Render it anywhere inside `Channel`, for example in a header of your own.

### Announcements as pinned messages

Pin a message to keep it reachable, for example a link or the rules of the chat. The channel's pinned messages are in `channel.pinnedMessagesPaginator`, and jumping to one works as described in [Jumping to a message](#jumping-to-a-message).

The Pin and Unpin actions show only to users with the `pin-message` capability, so viewers who may not pin don't see them. [Pinned messages](https://getstream.io/chat/docs/sdk/react-native/features/messaging/pinned-messages/#turning-it-on-or-off) explains who has it.

### Slow mode

Slow mode makes each user wait between messages, which keeps a busy chat readable. Turn it on with `Channel.enableSlowMode(seconds)`. Users with the `skip-slow-mode` capability, usually moderators and admins, don't wait. See [Slow mode](https://getstream.io/chat/docs/sdk/react-native/features/channels/slow-mode/).

### Moderation

From the message menu, viewers can flag a message and mute or block its author, and moderators can delete any message. The Flag action shows only for users with the `flag-message` capability, Delete with `delete-any-message` or `delete-own-message`, and Ban with `ban-channel-members`. Mute and Block have no capability check, so every viewer sees them on other users' messages. [Flagging, muting, blocking and bans](https://getstream.io/chat/docs/sdk/react-native/features/moderation-and-safety/flagging-muting-blocking-and-bans/) covers them. Block lists and automated moderation are set on the server, see [Moderation](https://getstream.io/chat/docs/javascript/moderation/).

### Threads

A stream audience rarely needs threads. Turn replies off for the channel type on the server, or leave `onThreadSelect` off the message list and remove Thread Reply from the message menu, as the [Livestream message list](https://getstream.io/chat/docs/sdk/react-native/ui-cookbooks/message-list/livestream-message-list/) UI cookbook does.

## Reference

- [MessageList](https://getstream.io/chat/docs/sdk/react-native/component-reference/message-list/messagelist/) and [MessageFlashList](https://getstream.io/chat/docs/sdk/react-native/component-reference/message-list/messageflashlist/): the `isLiveStreaming` prop.
- [Message paginator](https://getstream.io/chat/docs/sdk/react-native/reference/state/message-paginator/): `maxLoadedItems`, `stateThrottleMs` and `setPruningSuspended`.
- [Channel and channel state](https://getstream.io/chat/docs/sdk/react-native/reference/state/channel-and-channel-state/): `watcherCount`, `markReadLocally` and `enableSlowMode`.
- [Configuration tree](https://getstream.io/chat/docs/sdk/react-native/reference/state/configuration-tree/): `channel.messagePaginator`, `channel.readEvents` and `client.messageDelivery`.
- [Performance](https://getstream.io/chat/docs/sdk/react-native/features/app-experience/performance/): page size, throttling, the memory cap and measuring.
- [Livestream best practices](https://getstream.io/chat/docs/javascript/livestream-best-practices/): the server side of a livestream.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/features/channels/livestream-chat/](https://getstream.io/chat/docs/sdk/react-native/features/channels/livestream-chat/).