Wednesday, April 29, 2020

A Working Video Application

Looking at these tools, I'm not quite sure where to start. As I often do, when forced into a corner I try and find a working example.  Roku offer a github repository of samples, one of which is a VideoExample.  Unfortunately, at least in my environment, the official sample doesn't work any better than mine - it starts and then hangs trying to load the video.  It doesn't help that their example has suffered software rot and references a URL that even curl can't load - although the problem is just that the server has been made secure in the meantime and the URL needs to be changed to reflect that.

There are a number of other problems with the sample, including only supporting SD resolution, requiring the Roku to switch to 720p - which my monitor does not support.

I went and looked at the access logs - the file is not being referenced.

Using wireshark seemed to show up nothing, but it can be a difficult tool to use and I'm not 100% sure I'm capturing all the traffic I need too.

So what else can we do?  Well, the debugger looks promising.

Debugging BrightScript

I don't really know what I'm doing, so I'm guessing based on the description in the previous post.

I run up the application and wait for it to hang.  While I'm doing this, it spits out a bunch of debug statements:

04-19 19:53:03.969 [beacon.signal] |AppLaunchInitiate ---------> TimeBase(0)
04-19 19:53:03.972 [beacon.signal] |AppCompileInitiate --------> TimeBase(3 ms)
04-19 19:53:03.973 [scrpt.cmpl] Compiling 'Hello World', id 'dev'
04-19 19:53:03.992 [scrpt.load.mkup] Loading markup dev 'Hello World'
04-19 19:53:03.999 [scrpt.unload.mkup] Unloading markup dev 'Hello World'
04-19 19:53:04.008 [scrpt.parse.mkup.time] Parsed markup dev 'Hello World' in 16 milliseconds

------ Compiling dev 'Hello World' ------
04-19 19:53:04.028 [scrpt.ctx.cmpl.time] Compiled 'Hello World', id 'dev' in 13 milliseconds
04-19 19:53:04.041 [scrpt.proc.mkup.time] Processed markup dev 'Hello World' in 9 milliseconds
04-19 19:53:04.046 [beacon.signal] |AppCompileComplete --------> Duration(74 ms), 2.14 KiP
04-19 19:53:04.084 [ui.frm.plugin.running.enter] Entering PLUGIN_RUNNING for dev
04-19 19:53:04.086 [beacon.signal] |AppLaunchInitiate ---------> TimeBase(0)
04-19 19:53:06.727 [scrpt.ctx.run.enter] UI: Entering 'Hello World', id 'dev'

------ Running dev 'Hello World' main ------
in showChannelSGScreen

OK, well, so far, so good.  We have the message telling us it is setting up the screen.  But then ... nothing.

The instructions say to press "Ctrl C", so I do that and am rewarded with a slew of information and then a debugger prompt.  Excellent.

^C
BrightScript Micro Debugger.
Enter any BrightScript statement, debug commands, or HELP.

Suspending threads...
Thread selected:  0*   pkg:/source/Main.brs(20)                msg = wait(0, m.port)

Current Function:
012:      m.port = CreateObject("roMessagePort")
013:      screen.setMessagePort(m.port)
014:
015:      'Create a scene and load /components/helloworld.xml'
016:      scene = screen.CreateScene("HelloWorld")
017:      screen.show()
018:
019:      while(true)
020:*         msg = wait(0, m.port)
021:          msgType = type(msg)
022:          if msgType = "roSGScreenEvent"
023:              if msg.isScreenClosed() then return
024:          end if
Break in 20
020:         msg = wait(0, m.port)
Backtrace:
#0  Function main() As Void
   file/line: pkg:/source/Main.brs(20)
Local Variables:
global           Interface:ifGlobal
m                roAssociativeArray refcnt=2 count:1
screen           roSGScreen refcnt=1
scene            roSGNode:HelloWorld refcnt=1
msg              <uninitialized>
msgtype          <uninitialized>
Threads:
ID    Location                                Source Code
 0*   pkg:/source/Main.brs(20)                msg = wait(0, m.port)
  *selected

Brightscript Debugger>

What I think I want to do is dig into the video component, but how to find it?  There's a command bsc which lists all the active components.  Let's try that.

Brightscript Debugger> bsc
                 roGlobal refcnt=1  [<roGlobal>]
       roAssociativeArray refcnt=2  [{port:<roMessagePort>}]
               roSGScreen refcnt=1  [<roSGScreen>]
                   roList refcnt=1  [L[]]
            roMessagePort refcnt=2  [<roMessagePort>]
      roSGNode:HelloWorld refcnt=1  [<roSGNode>]
6 component(s) total.

On second thoughts, I'm intimidated by that.  I'm not quite sure what do with that.  Let's try another tack.  The listing above showed me the variables that are in scope, so let's try looking into those.  The scene seems like the root of everything good, so let's print that and see what we get.

Brightscript Debugger> print scene
<Component: roSGNode:PlayVideo> =
{
    backExitsScene: true
    backgroundColor: 589505535
    backgroundUri: ""
    currentDesignResolution: <Component: roAssociativeArray>
    dialog: <Component: roInvalid>
    limitBackgroundToUIResolution: true
    childRenderOrder: "last"
    clippingRect: <Component: roAssociativeArray>
    enableRenderTracking: false
    inheritParentOpacity: true
    inheritParentTransform: true
    muteAudioGuide: false
    opacity: 1
    renderPass: 0
    renderTracking: "disabled"
    rotation: 0
    scale: <Component: roArray>
    scaleRotateCenter: <Component: roArray>
    translation: <Component: roArray>
    visible: true
    change: <Component: roAssociativeArray>
    focusable: true
    focusedChild: <Component: roSGNode:Video>
    id: ""
}

I feel this should have a list of children but I'm not really seeing that.  But there is a focusedChild which is the Video component, so that seems like it might be worth further investigation.

Brightscript Debugger> print scene.focusedChild
<Component: roSGNode:Video> =
{
    allowOptionsKeyOverride: false
    alwaysShowVideoPlanes: false
    audioFormat: ""
    audioTrack: ""
    autoPlayAfterSeek: true
    availableAudioTracks: <Component: roArray>
    availableSubtitleTracks: <Component: roArray>
    bifDisplay: <Component: roSGNode:BifDisplay>
    bufferingBar: <Component: roSGNode:ProgressBar>
    bufferingBarVisibilityAuto: true
    bufferingBarVisibilityBounds: <Component: roAssociativeArray>
    bufferingBarVisibilityHint: false
    bufferingStatus: invalid
    bufferingTextColor: 0
    capFontPath: ""
    capFontSize: 0
    captionStyle: invalid
    cgms: "norestriction"
    clipId: 0
    completedStreamInfo: invalid
    content: <Component: roInvalid>
    contentBlocked: false
    contentIndex: -1
    contentIsPlaylist: false
    control: "play"
    currentAudioTrack: ""
    currentSubtitleTrack: ""
    disableScreenSaver: false
    downloadedSegment: invalid
    duration: 0
    enableScreenSaverWhilePlaying: false
    enableTrickPlay: true
    enableUI: true
    errorCode: -4
    errorInfo: <Component: roAssociativeArray>
    errorMsg: "No streams were provided for playback."
    errorStr: "player agent play"
    globalCaptionMode: "Instant replay"
    height: 0
    licenseStatus: invalid
    loop: false
    manifestData: invalid
    maxVideoDecodeResolution: <Component: roArray>
    mute: false
    nextContentIndex: -1
    notificationInterval: 0.5
    pauseBufferEnd: 0
    pauseBufferOverflow: false
    pauseBufferPosition: 0
    pauseBufferStart: 0
    position: 0
    retrievingBar: <Component: roSGNode:ProgressBar>
    retrievingBarVisibilityAuto: true
    retrievingBarVisibilityBounds: <Component: roAssociativeArray>
    retrievingBarVisibilityHint: true
    retrievingTextColor: 0
    seek: 0
    seekClip: invalid
    state: "error"
    streamInfo: invalid
    streamingSegment: invalid
    subtitleTrack: ""
    supplementaryAudioVolume: 50
    suppressCaptions: false
    thumbnailTiles: invalid
    timedMetaData: invalid
    timedMetaData2: invalid
    timedMetaDataSelectionKeys: <Component: roArray>
    timeToStartStreaming: 0
    tracks: <Component: roArray>
    trickPlayBackground: <Component: roInvalid>
    trickPlayBar: <Component: roSGNode:TrickPlayBar>
    trickPlayBarVisibilityAuto: true
    trickPlayBarVisibilityHint: false
    videoFormat: ""
    width: 0
    childRenderOrder: "last"
    clippingRect: <Component: roAssociativeArray>
    enableRenderTracking: false
    inheritParentOpacity: true
    inheritParentTransform: true
    muteAudioGuide: false
    opacity: 1
    renderPass: 0
    renderTracking: "disabled"
    rotation: 0
    scale: <Component: roArray>
    scaleRotateCenter: <Component: roArray>
    translation: <Component: roArray>
    visible: true
    change: <Component: roAssociativeArray>
    focusable: true
    focusedChild: <Component: roSGNode:Video>
    id: "playit"
}

I don't know about you, but what jumps out to me in this are the error fields:

    errorCode: -4
    errorInfo: <Component: roAssociativeArray>
    errorMsg: "No streams were provided for playback."
    errorStr: "player agent play"

No streams were provided for playback?  What?  Were you not looking?  Oh, and by the way, thanks for hiding a fairly important error message deep in your object hierarchy rather than printing it out on the console where we can all benefit ...

One Day Later

Not really knowing what else to do, I walked away.  But I kept turning this over in my mind and from time to time googling this and that as they occurred to me.

I checked my model number and OS level in the Settings/System/About screen to make sure that everything I thought was supported really was and, yes, I'm on the latest version of the OS and my model - a 2710X - is still supported.

The console stream - while not telling me about the small matter of there not being any streams - did keep telling me that my streamformat wasn't valid.  I couldn't - and still can't - figure out why that would be.  But it made me start to think about XML and types.  In XML everything is an element or a string.  But that doesn't mean that the two are interchangeable.

And it occurred to me that although I was setting the content to what seemed like a reasonable value, that the Roku might be expecting an element rather than a string.  Comparing what I'd done to what the sample application had done, I realized that they had done a lot more in their component's init() method, possibly for a good reason.

I reworked my playvideo.xml as follows:
<?xml version="1.0" encoding="utf-8" ?><component name="PlayVideo" extends="Scene">     <children>      <ContentNode id="damian" url="http://www.gmmapowell.com/downloads/Damian2018.mp4" StreamFormat="mp4"      />      <Video id="playit"         control="play"      width="0"       height="0"       />    </children><script type="text/brightscript" ><![CDATA[  function init()    video = m.top.findNode("playit")    video.content = m.top.findNode("damian")    video.setFocus(true)    video.control = "play"  end function]]></script></component>
This still creates the two components using XML, but connects them using BrightScript and "finding" the content node to attach it to the video node.

What do you know? It works.

Summary

Debugging a Roku application is much like debugging anything else - it's equal parts tools, investigation and inspiration.  This problem was solved by finding out what the problem was using the debugger, then applying research and inspiration to understand what could be the issue and then fixing it.

The result is the ability to what some quality Chiefs' rugby

No comments:

Post a Comment