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

# Recorded Room Playback

> Observe recorded room availability and pass SDK playback URLs to your player.

Recorded playback becomes available after a room finishes broadcasting and the backend publishes recorded playback metadata. The social.plus SDK owns room status, recorded playback URLs, recorded thumbnails, and recorded resolution metadata. Your app owns the actual player, queueing, seek controls, buffering, and retry UI.

<Note>
  This page covers SDK-recorded playback data. AVPlayer, ExoPlayer, HLS.js, browser autoplay handling, DRM, captions, and player UI are app-owned concerns after the SDK returns a recorded playback URL.
</Note>

## Platform Surface

| Platform   | Observe availability                                             | Recorded metadata                                                 | URL refresh helper                      | Notes                                                                                   |
| ---------- | ---------------------------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------- |
| TypeScript | `RoomRepository.getRoom(roomId, callback)`                       | `room.recordedPlaybackInfos[]`, `room.recordedResolution`         | `RoomRepository.getRecordedUrl(roomId)` | `getRecordedUrl()` returns a URL and optional expiry timestamp.                         |
| iOS        | `AmityRoomRepository().getRoom(withId:)`                         | `room.recordedData[]`, `room.recordedResolution`                  | Re-observe or fetch the room object     | Retain the returned `AmityNotificationToken` while observing.                           |
| Android    | `AmityVideoClient.newRoomRepository().getRoom(roomId)`           | `room.getRecordedPlaybackInfos()`, `room.getRecordedResolution()` | `getRecordedUrls(roomId)`               | `getRecordedUrls()` returns URL strings only.                                           |
| Flutter    | No current public room recorded playback API found in this audit | Not available                                                     | Not available                           | The Flutter SDK source exposes older stream APIs, not the room broadcasting repository. |

## Parameters

| Concept               | TypeScript                                  | iOS                                      | Android                                                |
| --------------------- | ------------------------------------------- | ---------------------------------------- | ------------------------------------------------------ |
| Observe room          | `RoomRepository.getRoom(roomId, callback)`  | `AmityRoomRepository().getRoom(withId:)` | `AmityVideoClient.newRoomRepository().getRoom(roomId)` |
| Recorded status       | `room.status === "recorded"`                | `room.status == .recorded`               | `room.getStatus() == AmityRoomStatus.RECORDED`         |
| Processing status     | `room.status === "ended"`                   | `room.status == .ended`                  | `room.getStatus() == AmityRoomStatus.ENDED`            |
| Playback URL metadata | `room.recordedPlaybackInfos[].url`          | `room.recordedData[].playbackUrl`        | `room.getRecordedPlaybackInfos()[].url`                |
| Thumbnail metadata    | `room.recordedPlaybackInfos[].thumbnailUrl` | `room.recordedData[].thumbnailUrl`       | `room.getRecordedPlaybackInfos()[].thumbnailUrl`       |
| Recorded resolution   | `room.recordedResolution`                   | `room.recordedResolution`                | `room.getRecordedResolution()`                         |
| Fresh URL helper      | `RoomRepository.getRecordedUrl(roomId)`     | Re-observe `AmityRoom`                   | `getRecordedUrls(roomId)`                              |

## Wait for Recorded Status

Observe the room while the playback screen is open. Treat `ended` as a processing state and only hand a recorded URL to your player after the room status becomes `recorded`.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { RoomRepository } from "@amityco/ts-sdk";

  function observeRecordedAvailability(roomId: string): Amity.Unsubscriber {
    return RoomRepository.getRoom(roomId, snapshot => {
      if (snapshot.error) {
        handleError(snapshot.error);
        return;
      }

      const room = snapshot.data;

      if (room.status === "recorded" && room.recordedPlaybackInfos.length > 0) {
        showSuccessMessage(room.recordedPlaybackInfos[0].url);
        return;
      }

      if (room.status === "ended") {
        showSuccessMessage("recording-processing");
        return;
      }

      showSuccessMessage(room.status);
    });
  }
  ```

  ```kotlin Android theme={null}
  import com.amity.socialcloud.sdk.api.video.AmityVideoClient
  import com.amity.socialcloud.sdk.model.video.room.AmityRoomStatus

  val disposable = AmityVideoClient.newRoomRepository()
      .getRoom(roomId)
      .subscribe(
          { room ->
              when (room.getStatus()) {
                  AmityRoomStatus.RECORDED -> {
                      val firstUrl = room.getRecordedPlaybackInfos()
                          .mapNotNull { it.url }
                          .firstOrNull()

                      showSuccessMessage(firstUrl ?: "recorded")
                  }
                  AmityRoomStatus.ENDED -> {
                      showSuccessMessage("recording-processing")
                  }
                  else -> {
                      showSuccessMessage(room.getStatus())
                  }
              }
          },
          { error -> handleGeneralError(error) }
      )
  ```

  ```swift iOS theme={null}
  var roomObservationToken: AmityNotificationToken?
  let roomObject = AmityRoomRepository().getRoom(withId: roomId)

  roomObservationToken = roomObject.observe { liveObject, error in
      if let error {
          handleGeneralError(error)
          return
      }

      guard let room = liveObject.snapshot else { return }

      switch room.status {
      case .recorded:
          showSuccessMessage(room.recordedData.first?.playbackUrl)
      case .ended:
          showSuccessMessage("recording-processing")
      default:
          showSuccessMessage(room.status.rawValue)
      }
  }
  ```
</CodeGroup>

## Read Recorded Sources

Recorded metadata can contain more than one playback item. Preserve the SDK order unless your product has a backend-defined reason to reorder segments.

<CodeGroup>
  ```typescript TypeScript theme={null}
  type RecordedPlaybackSource = {
    url: string;
    thumbnailUrl: string;
  };

  function recordedPlaybackSources(room: Amity.Room): RecordedPlaybackSource[] {
    if (room.status !== "recorded") {
      return [];
    }

    return room.recordedPlaybackInfos.map(info => ({
      url: info.url,
      thumbnailUrl: info.thumbnailUrl,
    }));
  }
  ```

  ```kotlin Android theme={null}
  import com.amity.socialcloud.sdk.model.video.room.AmityRoom
  import com.amity.socialcloud.sdk.model.video.room.AmityRoomStatus

  data class RecordedPlaybackSource(
      val url: String,
      val thumbnailUrl: String?
  )

  fun recordedPlaybackSources(room: AmityRoom): List<RecordedPlaybackSource> {
      if (room.getStatus() != AmityRoomStatus.RECORDED) {
          return emptyList()
      }

      return room.getRecordedPlaybackInfos().mapNotNull { info ->
          info.url?.let { url ->
              RecordedPlaybackSource(
                  url = url,
                  thumbnailUrl = info.thumbnailUrl
              )
          }
      }
  }
  ```

  ```swift iOS theme={null}
  struct RecordedPlaybackSource {
      let playbackUrl: String
      let thumbnailUrl: String
  }

  func recordedPlaybackSources(for room: AmityRoom) -> [RecordedPlaybackSource] {
      guard room.status == .recorded else { return [] }

      return room.recordedData
          .filter { !$0.playbackUrl.isEmpty }
          .map { data in
              RecordedPlaybackSource(
                  playbackUrl: data.playbackUrl,
                  thumbnailUrl: data.thumbnailUrl
              )
          }
  }
  ```
</CodeGroup>

## Refresh a Playback URL

Refresh recorded playback data before starting playback, after a long pause, or when your player reports an expired or unauthorized media URL.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { RoomRepository } from "@amityco/ts-sdk";

  const recordedUrl = await RoomRepository.getRecordedUrl(roomId);

  showSuccessMessage(recordedUrl.url);
  showSuccessMessage(recordedUrl.expiresAt);
  ```

  ```kotlin Android theme={null}
  import com.amity.socialcloud.sdk.api.video.AmityVideoClient

  val disposable = AmityVideoClient.newRoomRepository()
      .getRecordedUrls(roomId)
      .subscribe(
          { urls -> showSuccessMessage(urls.firstOrNull() ?: "no-recorded-url") },
          { error -> handleGeneralError(error) }
      )
  ```

  ```swift iOS theme={null}
  var refreshToken: AmityNotificationToken?
  let roomObject = AmityRoomRepository().getRoom(withId: roomId)

  refreshToken = roomObject.observeOnce { liveObject, error in
      if let error {
          handleGeneralError(error)
          return
      }

      guard let room = liveObject.snapshot,
            room.status == .recorded else {
          return
      }

      showSuccessMessage(room.recordedData.first?.playbackUrl)
  }
  ```
</CodeGroup>

## Status Handling

| Room status                 | Recorded playback behavior                                                                          |
| --------------------------- | --------------------------------------------------------------------------------------------------- |
| `idle`                      | No recording exists. Show a waiting or not-started state.                                           |
| `live` / `waitingReconnect` | Use live playback, not recorded playback.                                                           |
| `ended`                     | Broadcast has ended, but recording metadata may still be processing. Keep observing or offer retry. |
| `recorded`                  | Read recorded playback metadata and pass a URL to your player.                                      |
| `terminated`                | TypeScript room status for a terminated room. Do not assume recorded playback is available.         |
| `error`                     | Show a recoverable error state and let the viewer retry or leave.                                   |

<Warning>
  Do not treat `ended` as playable recorded content. Wait for `recorded` status or a successful platform URL refresh response before handing a recorded URL to the player.
</Warning>

## Segment and Player Boundary

| Area         | SDK responsibility                                                          | App/player responsibility                                                      |
| ------------ | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Availability | Room status updates and recorded metadata                                   | Processing, empty, retry, and unavailable UI                                   |
| Source list  | Recorded URLs, thumbnails, and recorded resolution                          | Segment queueing, autoplay policy, seek controls, buffering, captions, and DRM |
| Freshness    | TS `getRecordedUrl()`, Android `getRecordedUrls()`, iOS room re-observation | Retry timing, expired URL recovery, and player reload                          |
| Cleanup      | Unsubscribe, invalidate tokens, or dispose Rx subscriptions                 | Release player instances and audio/video resources                             |

<Info>
  Multiple recorded playback items can represent multiple files for one recording. Preserve the SDK order and let the player layer decide whether to play only the first URL or queue all URLs.
</Info>

## Related Topics

<CardGroup cols={3}>
  <Card title="Live Room Viewing" icon="signal-stream" href="./live-viewing">
    Discover room posts, observe live rooms, and choose playback sources.
  </Card>

  <Card title="Start Broadcasting" icon="radio" href="./start-broadcasting">
    Fetch broadcaster credentials and start the host-side media session.
  </Card>

  <Card title="Manage Rooms" icon="settings" href="./manage-rooms">
    Observe, query, update, stop, or delete room records.
  </Card>
</CardGroup>
