Skip to main content

Live Room Viewing

This guide covers how to view and play live rooms in real-time, including room discovery, player setup, and handling different room states.

Room Discovery & Loading

Retrieve Room from Post

Get a room from a post to access room metadata and playback information:
import AmitySDK
import AVKit

class LiveRoomViewer {
    
    func getRoomFromPost(_ post: AmityPost) {
        guard let roomData = post.getRoomInfo() else {
            print("No room data available")
            return
        }
        
        // Access room information
        print("Room ID: \(roomData.roomId)")
        print("Status: \(roomData.status)")
        
        // Handle different room states
        switch roomData.status {
        case .live:
            // Room is currently live - ready to play
            if let liveUrl = roomData.getLivePlaybackUrl() {
                playLiveRoom(url: liveUrl)
            }
        case .recorded:
            // Room has ended and recording is available
            let recordedInfos = roomData.getRecordedPlaybackInfos()
            if let firstUrl = recordedInfos.first?.url {
                playRecordedRoom(url: firstUrl)
            }
        case .ended:
            // Room ended but recording may not be ready
            showMessage("Room has ended, recording will be available soon")
        case .idle:
            // Room created but not started
            showMessage("Room hasn't started yet")
        default:
            break
        }
    }
    
    private func playLiveRoom(url: String) {
        guard let videoURL = URL(string: url) else { return }
        
        let player = AVPlayer(url: videoURL)
        let playerViewController = AVPlayerViewController()
        playerViewController.player = player
        
        present(playerViewController, animated: true) {
            player.play()
        }
    }
}

Room States & Status

Understanding room lifecycle and handling state transitions:

Room Status Types

StatusDescriptionPlayback Action
idleRoom created but not startedShow “Coming Soon” message
liveCurrently broadcastingUse getLivePlaybackUrl()
endedBroadcasting finished, processing recordingShow “Processing” message
recordedRecording available for playbackUse getRecordedPlaybackInfos()

Real-Time State Monitoring

class RoomStateManager {
    private var roomToken: AmityNotificationToken?
    
    func observeRoomState(roomId: String) {
        let roomRepository = AmityVideoClient.newRoomRepository()
        let liveRoom = roomRepository.getRoom(roomId)
        
        roomToken = liveRoom.observe { [weak self] liveObject, error in
            guard let room = liveObject.object else { return }
            self?.updateUI(for: room)
        }
    }
    
    private func updateUI(for room: AmityRoom) {
        DispatchQueue.main.async {
            switch room.status {
            case .idle:
                self.hidePlayer()
                self.showWaitingMessage("Room will start soon...")
            case .live:
                self.showPlayer()
                if let liveUrl = room.getLivePlaybackUrl() {
                    self.startLivePlayback(url: liveUrl)
                }
                self.showLiveIndicator(true)
            case .ended:
                self.stopPlayback()
                self.showMessage("Room has ended. Recording will be available soon.")
            case .recorded:
                self.showPlayer()
                let recordedInfos = room.getRecordedPlaybackInfos()
                if let firstUrl = recordedInfos.first?.url {
                    self.startRecordedPlayback(url: firstUrl)
                }
                self.showLiveIndicator(false)
            default:
                break
            }
        }
    }
}

Live Room Playback

Getting Live Playback URL

Use getLivePlaybackUrl() to get the URL for live room playback:
func getLivePlaybackUrl(from room: AmityRoom) -> String? {
    guard room.status == .live else {
        print("Room is not live")
        return nil
    }
    
    return room.getLivePlaybackUrl()
}

Platform-Specific Player Setup

iOS - AVPlayer for Live Rooms

import AVKit

class LiveRoomPlayer {
    private var player: AVPlayer?
    private var playerViewController: AVPlayerViewController?
    
    func setupPlayer() -> AVPlayerViewController {
        playerViewController = AVPlayerViewController()
        return playerViewController!
    }
    
    func playLiveRoom(url: String) {
        guard let videoURL = URL(string: url) else { return }
        
        player = AVPlayer(url: videoURL)
        playerViewController?.player = player
        
        // Start playback
        player?.play()
    }
    
    func pause() {
        player?.pause()
    }
    
    func resume() {
        player?.play()
    }
    
    func cleanup() {
        player?.pause()
        player = nil
        playerViewController = nil
    }
}

Android - ExoPlayer for Live Rooms

import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView

class LiveRoomPlayer(private val context: Context) {
    
    private var exoPlayer: ExoPlayer? = null
    
    fun setupPlayer(playerView: PlayerView) {
        exoPlayer = ExoPlayer.Builder(context).build()
        playerView.player = exoPlayer
        
        // Add player event listeners
        exoPlayer?.addListener(object : Player.Listener {
            override fun onPlaybackStateChanged(state: Int) {
                when (state) {
                    Player.STATE_READY -> Log.d("Player", "Ready to play")
                    Player.STATE_BUFFERING -> Log.d("Player", "Buffering...")
                    Player.STATE_ENDED -> Log.d("Player", "Playback ended")
                    Player.STATE_IDLE -> Log.d("Player", "Player idle")
                }
            }
            
            override fun onPlayerError(error: PlaybackException) {
                Log.e("Player", "Playback error: ${error.message}")
            }
        })
    }
    
    fun playLiveRoom(url: String) {
        val mediaItem = MediaItem.fromUri(url)
        exoPlayer?.setMediaItem(mediaItem)
        exoPlayer?.prepare()
        exoPlayer?.playWhenReady = true
    }
    
    fun pause() {
        exoPlayer?.pause()
    }
    
    fun resume() {
        exoPlayer?.play()
    }
    
    fun release() {
        exoPlayer?.release()
        exoPlayer = null
    }
}

TypeScript - HTML5 Video Player

class LiveRoomPlayer {
    private videoElement: HTMLVideoElement;
    private hls: Hls | null = null;
    
    constructor(videoElementId: string) {
        this.videoElement = document.getElementById(videoElementId) as HTMLVideoElement;
        this.setupEventListeners();
    }
    
    async playLiveRoom(url: string) {
        try {
            if (url.includes('.m3u8')) {
                await this.setupHLSPlayer(url);
            } else {
                this.videoElement.src = url;
                await this.videoElement.play();
            }
        } catch (error) {
            console.error('Failed to play live room:', error);
            throw error;
        }
    }
    
    private async setupHLSPlayer(hlsUrl: string) {
        if (Hls.isSupported()) {
            this.hls = new Hls();
            this.hls.loadSource(hlsUrl);
            this.hls.attachMedia(this.videoElement);
            
            this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
                this.videoElement.play();
            });
            
            this.hls.on(Hls.Events.ERROR, (event, data) => {
                console.error('HLS error:', data);
            });
        } else if (this.videoElement.canPlayType('application/vnd.apple.mpegurl')) {
            this.videoElement.src = hlsUrl;
            await this.videoElement.play();
        } else {
            throw new Error('HLS not supported');
        }
    }
    
    private setupEventListeners() {
        this.videoElement.addEventListener('loadstart', () => console.log('Room loading started'));
        this.videoElement.addEventListener('canplay', () => console.log('Room ready to play'));
        this.videoElement.addEventListener('error', (e) => console.error('Player error:', e));
        this.videoElement.addEventListener('waiting', () => console.log('Buffering...'));
        this.videoElement.addEventListener('playing', () => console.log('Playback started'));
    }
    
    pause() { this.videoElement.pause(); }
    resume() { this.videoElement.play(); }
    
    destroy() {
        this.hls?.destroy();
        this.videoElement.pause();
        this.videoElement.src = '';
    }
}

Error Handling & Recovery

Common Playback Issues

class PlaybackErrorHandler {
    
    func handlePlayerError(_ error: Error) {
        let errorDescription = error.localizedDescription.lowercased()
        
        if errorDescription.contains("network") {
            showAlert("Network Error", message: "Please check your internet connection")
            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                self.retryPlayback()
            }
        } else if errorDescription.contains("format") {
            showAlert("Format Error", message: "Unsupported video format")
        } else if errorDescription.contains("permission") {
            showAlert("Access Denied", message: "You don't have permission to view this room")
        } else {
            showAlert("Playback Error", message: error.localizedDescription)
        }
    }
    
    private func retryPlayback() {
        // Implement retry logic
    }
}
Concurrency Limit: There is a limitation on the maximum number of concurrent live rooms. Contact community.social.plus.co with your use case to determine if limits can be raised.
Performance Tip: For mobile applications, monitor device temperature and network conditions. Consider reducing video quality during poor network conditions to maintain playback stability.

Next Steps

With live viewing implemented: