Events Playback

Pre-requisite: The user has to be signed in to perform the following operations.

Events video urls are in m3u8 format which can be streamed over via exoplayer and HlsMediaSource.


Generating an Event Playback Url.

The video URL in the event object may or may not contain an Authorization query param. If the Url contains the query params you are to replace it.

  • Start by fetching the Event Tokens
  • For the event that you need to play replace the Autorization in the video url with the token after matching the bucket names
  • The Newly constructed url can be played using HlsMediaSource and exoplayer.
  • You will also need to use DefaultHttpDataSource to pass authentication to exoplayer.

For Example: If the video url in the event is https://www.example.com/video.m3u8 and the event entity has the bucket name 180-days then we need to look the tokens and look for the bucket named 180-days this object will give us the authentication token so the newly formed url would be https://www.example.cpm/video.m3u8?Authorization=123123123123123


Using Exoplayer and Jetpack Compose

@Composable
private fun Player(event: Event, token: String) {
  val exoPlayer = remember {
    ExoPlayer
      .Builder(context)
      .setRenderersFactory(
        DefaultRenderersFactory(context).setEnableDecoderFallback(true)
      )
      .build()
  }
  LaunchedEffect(event.videoUrl) {
    val uri = Uri.parse(event.videoUrl.orEmpty())
    val uriBuilder = uri.buildUpon().clearQuery()
    for (paramKey in uri.getQueryParameterNames()) {
      val value: String =
        if (paramKey == "Authorization") token else uri.getQueryParameter(paramKey)
      uriBuilder.appendQueryParameter(paramKey, value)
    }
    exoPlayer.apply {
      val dataSourceFactory =
        DefaultHttpDataSource.Factory()
          .setDefaultRequestProperties(hashMapOf("Authorization" to token))
      val mediaSource =
        HlsMediaSource.Factory(dataSourceFactory)
          .createMediaSource(MediaItem.fromUri(uriBuilder))
      setMediaSource(mediaSource)
      addListener(object: Player.Listener {
        override fun onPlaybackStateChanged(
          playerState: Int
        ) {
          // Handle this
        }

        override fun onTimelineChanged(
          timeline: Timeline,
          reason: Int
        ) {
          super.onTimelineChanged(timeline, reason)
          // Handle This
        }

        override fun onPlayerError(
          error: PlaybackException
        ) {
          // Handle This
        }
      })
      prepare()
      seekTo(state.currentPosition)
      playWhenReady = true
    }
  }
  DisposableEffect(Unit) {
    onDispose {
      exoPlayer.release()
    }
  }
  Box(
    modifier = Modifier.align(Alignment.Center),
  ) {
    val context = LocalContext.current
    AndroidView(
      modifier = Modifier,
      factory = {
        PlayerView(context).apply {
          hideController()
          resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
          useController = false
          player = exoPlayer
          keepScreenOn = true
        }
      },
    )
  }
}