components_GetPlaybackInfoTask.bs

import "pkg:/source/api/sdk.bs"
import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/utils/config.bs"
import "pkg:/source/utils/misc.bs"

sub init()
  m.log = log.Logger("GetPlaybackInfoTask")
  m.top.functionName = "getPlaybackInfoTask"
end sub

' Returns an array of playback info to be displayed during playback.
' In the future, with a custom playback info view, we can return an associated array.
sub getPlaybackInfoTask()
  sessions = api.sessions.Get({ "deviceId": m.global.device.serverDeviceName })

  ' Use cached playback info instead of making redundant API call
  if isValid(m.top.cachedPlaybackInfo)
    m.playbackInfo = m.top.cachedPlaybackInfo
  else
    ' No cached data available - this represents an unexpected error condition
    m.log.warn("cachedPlaybackInfo is invalid - Stream Information will not be displayed", {
      videoID: m.top.videoID
    })
    m.playbackInfo = invalid
  end if

  if isValid(sessions) and sessions.Count() > 0
    m.top.data = { playbackInfo: GetTranscodingStats(sessions[0]) }
  else
    m.top.data = { playbackInfo: [tr("Unable to get playback information")] }
  end if
end sub

function GetTranscodingStats(deviceSession)
  sessionStats = { data: [] }

  if isValid(deviceSession.TranscodingInfo) and deviceSession.TranscodingInfo.Count() > 0
    transcodingReasons = deviceSession.TranscodingInfo.TranscodeReasons
    videoCodec = deviceSession.TranscodingInfo.VideoCodec
    audioCodec = deviceSession.TranscodingInfo.AudioCodec
    totalBitrate = deviceSession.TranscodingInfo.Bitrate
    audioChannels = deviceSession.TranscodingInfo.AudioChannels

    if isValid(transcodingReasons) and transcodingReasons.Count() > 0
      sessionStats.data.push("<header>" + tr("Transcoding Information") + "</header>")
      for each item in transcodingReasons
        sessionStats.data.push("<b>• " + tr("Reason") + ":</b> " + item)
      end for
    end if

    if isValid(videoCodec)
      data = "<b>• " + tr("Video Codec") + ":</b> " + videoCodec
      if deviceSession.TranscodingInfo.IsVideoDirect
        data = data + " (" + tr("direct") + ")"
      end if
      sessionStats.data.push(data)
    end if

    if isValid(audioCodec)
      data = "<b>• " + tr("Audio Codec") + ":</b> " + audioCodec
      if deviceSession.TranscodingInfo.IsAudioDirect
        data = data + " (" + tr("direct") + ")"
      end if
      sessionStats.data.push(data)
    end if

    if isValid(totalBitrate)
      data = "<b>• " + tr("Total Bitrate") + ":</b> " + getDisplayBitrate(totalBitrate)
      sessionStats.data.push(data)
    end if

    if isValid(audioChannels)
      data = "<b>• " + tr("Audio Channels") + ":</b> " + Str(audioChannels)
      sessionStats.data.push(data)
    end if
  else
    sessionStats.data.push("<header>" + tr("Direct playing") + "</header>")
    sessionStats.data.push("<b>" + tr("The source file is entirely compatible with this client and the session is receiving the file without modifications.") + "</b>")
  end if

  if havePlaybackInfo()
    ' Find the first video stream (MediaStreams[0] might be subtitle/audio)
    videoStream = getFirstVideoStream(m.playbackInfo.mediaSources[0].MediaStreams)
    if not isValid(videoStream)
      ' No video stream found - skip Stream Information section
      return sessionStats
    end if

    stream = videoStream
    sessionStats.data.push("<header>" + tr("Stream Information") + "</header>")
    if isValid(stream.Container)
      data = "<b>• " + tr("Container") + ":</b> " + stream.Container
      sessionStats.data.push(data)
    end if
    if isValid(stream.Size)
      data = "<b>• " + tr("Size") + ":</b> " + stream.Size
      sessionStats.data.push(data)
    end if
    if isValid(stream.BitRate)
      data = "<b>• " + tr("Bit Rate") + ":</b> " + getDisplayBitrate(stream.BitRate)
      sessionStats.data.push(data)
    end if
    if isValid(stream.Codec)
      data = "<b>• " + tr("Codec") + ":</b> " + stream.Codec
      sessionStats.data.push(data)
    end if
    if isValid(stream.CodecTag)
      data = "<b>• " + tr("Codec Tag") + ":</b> " + stream.CodecTag
      sessionStats.data.push(data)
    end if
    if isValid(stream.VideoRangeType)
      data = "<b>• " + tr("Video range type") + ":</b> " + stream.VideoRangeType
      sessionStats.data.push(data)
    end if
    if isValid(stream.PixelFormat)
      data = "<b>• " + tr("Pixel format") + ":</b> " + stream.PixelFormat
      sessionStats.data.push(data)
    end if
    if isValid(stream.Width) and isValid(stream.Height)
      data = "<b>• " + tr("WxH") + ":</b> " + Str(stream.Width) + " x " + Str(stream.Height)
      sessionStats.data.push(data)
    end if
    if isValid(stream.Level)
      data = "<b>• " + tr("Level") + ":</b> " + Str(stream.Level)
      sessionStats.data.push(data)
    end if
  end if

  return sessionStats
end function

function havePlaybackInfo()
  if not isValid(m.playbackInfo)
    return false
  end if

  if not isValid(m.playbackInfo.mediaSources)
    return false
  end if

  if m.playbackInfo.mediaSources.Count() <= 0
    return false
  end if

  if not isValid(m.playbackInfo.mediaSources[0].MediaStreams)
    return false
  end if

  if m.playbackInfo.mediaSources[0].MediaStreams.Count() <= 0
    return false
  end if

  return true
end function

function getDisplayBitrate(bitrate)
  if bitrate > 1000000
    return Str(Fix(bitrate / 1000000)) + " Mbps"
  else
    return Str(Fix(bitrate / 1000)) + " Kbps"
  end if
end function