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
:
State | Description |
---|---|
StreamViewerState.BUSY | Viewer is transitioning into another state or busy doing internal work. |
StreamViewerState.IDLE | Viewer is ready to accept streams through StreamViewer.set . No stream was set. |
StreamViewerState.BUFFERING | Stream was set and will play as soon as buffering is finished. |
StreamViewerState.WATCHING | Stream was set and it is being played. |
StreamViewerState.PAUSED | Stream was set but playback was paused with pause() . Viewer is ready to play it as soon as play() is called. |
StreamViewerState.PAUSED_BUFFERING | Stream 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 containerAspectMode.CENTER_CROP
: video fills the container, some part of it might be croppedAspectMode.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.
}
})