Skip to main content
Version: 1.0.3

Viewing Streams

Viewing live streams means interacting with the io.video.videokit.live.viewer.StreamViewer interface. This interface provides functions to:

  • choose the Stream that should be played
  • play, pause and stop the stream
  • other playback options (StreamHost.mute, StreamHost.aspectMode)
  • listen to relevant events (StreamHost.addListener(StreamViewerListener))

Implementations#

We provide three StreamViewer implementations that you can choose from depending on your case. The usage varies a bit but they all share the same functionality from StreamViewer.

### StreamViewerController

A StreamViewerController is the low level implementation that, unlike the others, is detached from the UI. You will typically hold the controller instance in a ViewModel. Optionally, for perfect state restoration during configuration changes, we also recommend that you use Android's SavedStateHandle and pass it to the controller constructor.

class StreamingViewModel(state: SavedStateHandle) : ViewModel() {
val streamViewer = StreamViewerController(state)
override fun onCleared() {
super.onCleared()
streamViewer.release()
}
}

As you can see, the StreamViewerController must be released when you're done with it. In order to show the UI and start using the viewer, you must also call one of the bind methods as soon as you have a view container. For example, with a fragment:

class StreamingFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.streamViewer.bind(this, view.findViewById(R.id.streaming_container))
}
}

By passing in the fragment instance, the controller will use the fragment lifecycle to avoid memory leaks, so that there's no need to unbind.

StreamViewerFragment#

The StreamViewerFragment is a fragment implemented exactly as described above. It is the recommended implementation as it is very easy to use - no need to release or bind UI, because the fragment owns the views.

You can customize the fragment after it is attached or when creating it, thanks to the StreamViewerOptions class:

val options = StreamViewerOptions.build {
aspectMode(AspectMode.CENTER_INSIDE)
mute(false)
}
val fragment = StreamViewerFragment.newInstance(options)

StreamViewerView#

The StreamViewerView is a view that holds a controller, to be used for codebases that do not use fragments at all. Just like the controller:

  • The view must be released with release()
  • You must pass a fragment / activity / lifecycle with bind()

Usage#

Once you have chosen the stream viewer implementation, using it is pretty simple.

Viewer state#

At any moment, you can retrieve the viewer state using StreamViewer.state:

StateDescription
StreamViewerState.BUSYViewer is transitioning into another state or busy doing internal work.
StreamViewerState.IDLEViewer is ready to accept streams through StreamViewer.set. No stream was set.
StreamViewerState.BUFFERINGStream was set and will play as soon as buffering is finished.
StreamViewerState.WATCHINGStream was set and it is being played.
StreamViewerState.PAUSEDStream was set but playback was paused with pause(). Viewer is ready to play it as soon as play() is called.
StreamViewerState.PAUSED_BUFFERINGStream was set but playback was paused with pause(). Viewer is not ready to play because it needs to buffer more data.

You also have handy functions to navigate through the different states easily:

// Inform the viewer of the stream to be played, optionally playing it.
streamViewer.set(stream, play = false)
// Start playing the current stream, or pausing, or toggling.
streamViewer.play()
streamViewer.pause()
streamViewer.toggle()
// Unset the previously set stream. This will stop playback if it had started.
streamViewer.reset()
// abort everything. This signals cancellation to the error callback in StreamViewerListener.
streamViewer.abort()

You can also, at any moment, access the underlying Stream object through StreamViewer.stream.

Playback options#

You can use StreamViewer.mute to optionally mute or unmute audio from the stream.

We also offer different StreamViewer.aspectMode modes for video rendering:

  • AspectMode.FILL: video is stretched to match the container
  • AspectMode.CENTER_CROP: video fills the container, some part of it might be cropped
  • AspectMode.CENTER_INSIDE: video fills the container along one dimension only, no cropping

Events#

Events are subscribed to through the StreamViewer.addListener and StreamViewer.removeListener functions.

val streamViewer: StreamViewer = ...
streamViewer.addListener(object : StreamViewerListener {
override fun onError(error: VideoError) {
// Handle error!
}
override fun onStateChanged(@StreamViewerState.Value state: Int) {
// State has changed.
}
override fun onStreamChanged(stream: Stream?) {
// Stream has changed. This is called after set / reset, but also when the source
// stream is modified by the broadcaster. For example, if broadcaster pauses the stream,
// the stream object here will have stream.state == "paused".
}
override fun onMuteChanged(mute: Boolean) {
// Mute value was changed via StreamViewer.mute setter.
}
override fun onAspectModeChanged(@AspectMode.Value mode: Int) {
// Aspect mode was changed via StreamViewer.aspectMode setter.
}
})
Last updated on