Roku RAF Integration Assistance

910 views Asked by At

I am currently using Eclipse IDE with the Roku Developer Plugin to create a channel. I have experience in programming and code but brightscript has been a little more difficult for me to find my comfort with.

I have successfully created a channel that streams sufficiently. What I'm trying to do now is test the inclusion of RAF. I have tried looking through the code of an existing RAF template along with my current working streaming program.

Unfortunately the calls and variable names are different enough, and my ignorance is high enough I'm struggling to connect some dots. As I included the RAF library and code, or the code I thought I needed, the video no longer plays when I press the play button. The channel loads and the preview/details load just when I press play it does nothing.

Here is the code for that section of code on the page.

    Sub onItemSelected()
' first button is Play
if m.top.itemSelected = 0
    m.videoPlayer = CreateObject("roSGNode", "Video")
    m.videoPlayer.id="videoPlayer"
    m.videoPlayer.translation="[0, 0]"
    m.videoPlayer.width="1280"
    m.videoPlayer.height="720"
    m.videoPlayer.content   = m.top.content

    'show video player
    m.top.AppendChild(m.videoPlayer)

    m.videoPlayer.visible = true
    m.videoPlayer.setFocus(true)
    m.videoPlayer.control = "play"
    m.videoPlayer.observeField("state", "OnVideoPlayerStateChange")
    m.videoPlayer.observeField("visible", "onVideoVisibleChange")

    'THIS IS THE CODE I ADDED FROM THE RAF EXAMPLE
    'EVERYTHING IN BELOW THIS UNTIL THE END OF THE
    'SUB IF REMOVED THE VIDEO PLAYS.
    '--------------------------------------------------------------------------------
    adIface = Roku_Ads() 'RAF initialize
    print "Roku_Ads library version: " + adIface.getLibVersion()

    adIface.setDebugOutput(true) 'for debug pupropse

    'Indicates whether the default Roku backfill ad service URL 
    'should be used in case the client-configured URL fails (2 retries)
    'to return any renderable ads.
    adIface.setAdPrefs(true, 2)

    ' Normally, would set publisher's ad URL here.  Uncomment following line to do so.
    ' adIface.setAdUrl(m.videoContent.adUrl) 
    ' Otherwise uses default Roku ad server (with single preroll placeholder ad)

    'Returns available ad pod(s) scheduled for rendering or invalid, if none are available.
    adPods = adIface.getAds()

    playContent = true
    'render pre-roll ads
    if adPods <> invalid and adPods.count() > 0 then
        playContent = adIface.showAds(adPods)
    endif
    while(true)
        msg = wait(0, m.port)
        msgType = type(msg)

        if msgType = "roSGScreenEvent"
            if msg.isScreenClosed() then return
        else if msgType = "roSGNodeEvent"
           if (msg.GetNode() = "DetailsScreen")

                if msg.GetField() = "position" then
                    'render mid-roll ads
                    curPos = m.video.position
                    videoEvent = createPlayPosMsg(curPos)
                    adPods = adIface.getAds(videoEvent)
                    if adPods <> invalid and adPods.count() > 0
                        m.video.control = "stop"
                        playContent = adIface.showAds(adPods)
                    if playContent then
                            m.video.seek = curPos
                            m.video.control = "play"
                        endif
                    endif
                else if msg.GetField() = "state" then
                    curState = m.video.state
                    if curState = "finished" then
                        'render post-roll ads
                        videoEvent = createPlayPosMsg(curPos, true)
                        adPods = adIface.getAds(videoEvent)
                        m.video.control = "stop"
                        if adPods <> invalid and adPods.count() > 0
                            adIface.showAds(adPods)
                        end if
                        exit while
                    endif
                else if msg.GetField() = "navBack" then
                    'back button handling
                    if msg.GetData() = true then
                        m.video.control = "stop"
                        exit while
                    endif
                end if
            end if
        end if
    end while
'----------------------------------------------------------------------
'THIS IS THE END OF THE RAF CODE I INSERTED INTO THE VIDEO TEMPLATE.
end if

End Sub

On Eclipse it gives me an error that I have tried researching and figuring out but as I mentioned I'm stuck.

    Current Function:
    086:          m.videoPlayer.control = "play"
    087:          m.videoPlayer.observeField("state", "OnVideoPlayerStateChange")
    088:          m.videoPlayer.observeField("visible", "onVideoVisibleChange")
    089:          
    090:          'THIS IS THE CODE I ADDED FROM THE RAF EXAMPLE
    091:          'EVERYTHING IN BELOW THIS UNTIL THE END OF THE
    092:          'SUB IF REMOVED THE VIDEO PLAYS.
    093:          '--------------------------------------------------------------------------------
    094:*         adIface = Roku_Ads() 'RAF initialize
    095:          print "Roku_Ads library version: " + adIface.getLibVersion()
    096:  
    097:          adIface.setDebugOutput(true) 'for debug pupropse
    098:          
    Function Call Operator ( ) attempted on non-function. (runtime error &he0) in pkg:/components/screens/DetailsScreen/DetailsScreen.brs(94)
    094:         adIface = Roku_Ads() 'RAF initialize
    Backtrace:
    #0  Function onitemselected() As Void
       file/line: pkg:/components/screens/DetailsScreen/DetailsScreen.brs(94)
    Local Variables:
    global           Interface:ifGlobal
    m                roAssociativeArray refcnt=2 count:7
    adiface          <uninitialized>
    adpods           <uninitialized>
    playcontent      <uninitialized>
    msg              <uninitialized>
    msgtype          <uninitialized>
    curpos           <uninitialized>
    videoevent       <uninitialized>
    curstate         <uninitialized>
    roku_ads         <uninitialized>
    createplayposmsg <uninitialized>
    Threads:
    ID    Location                                Source Code
     0    pkg:/source/main.brs(34)                msg = wait(0, port)
     1*   ...ailsScreen/DetailsScreen.brs(94)     adIface = Roku_Ads() 'RAF initialize
      *selected

    Brightscript Debugger> 

I'm hoping to narrow down how to modify my channel to make this work. I did leave out this code:

    'RAF content params
        adIface.setContentId(m.videoContent.contentId)
        adIface.SetContentGenre(m.videoContent.contentGenre)

        'Nielsen content params
        adIface.enableNielsenDAR(true)
        adIface.setContentLength(m.videoContent.conntentLength)
        adIface.setNielsenProgramId(m.videoContent.nielsenProgramId)
        adIface.setNielsenGenre(m.videoContent.nielsenGenre)
        adIface.setNielsenAppId(m.videoContent.nielsenAppId)

Didnt want to work with Nielson stuff yet but not sure if I need to include it to make RAF work.

Thank you for your time, truly.

3

There are 3 answers

1
Nas Banov On

Also note you cannot invoke RAF from a render thread - which will be the next issue, looking at your code. It should either be done from the main thread or from a task thread (see https://github.com/rokudev/RAF4RSG-sample ).

2
Eugene Smoliy On

Make sure you have library imported at the top of your file:

Library "Roku_Ads.brs"

And entry in manifest file:

bs_libs_required=roku_ads_lib
1
Max Rojas On

Thank you everyone for your assistance. Nas Banov, I looked over the RAF example you linked to GitHub and I think I understand what I need to do just having difficulty seeing the road.

I have actually added the seperate .brs file to handle the RAF implementation as you suggested. The video is loaded into a grid from an external XML document as follows:

    <?xml version="1.0" encoding="UTF-8"?>
    <rss xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
      <channel>
        <title></title>
        <link />
        <description></description>
        <language></language>
        <pubDate></pubDate>
        <image>
          <title></title>
          <url></url>
          <width>-1</width>
          <height>-1</height>
        </image>

        <item>
          <title></title>
          <link></link>
          <description></description>
          <pubDate></pubDate>
          <guid isPermaLink="false"></guid>
          <media:content channels="2" bitrate="1328.0" duration="53"
            fileSize="8731706" framerate="23.976" height="720" 
            type="video/mp4" width="1280" isDefault="true" url="">
            <media:description></media:description>
            <media:keywords></media:keywords>
            <media:thumbnail url="" />
            <media:title></media:title>
          </media:content>
        </item>

      </channel>
    </rss>

I was thinking of adding the Nielson Necessary fields to this XML if I find a way to push the xml entries into the RAF integration for reporting.

This is the Details portion of the menu selection. After pressing a button on the Grid layout the Details screen loads. Here i reference the PlayerTasks.brs which has the RAF integration code in it.

' ********** Copyright 2016 Roku Corp.  All Rights Reserved. ********** 
 ' inits details screen
 ' sets all observers 
 ' configures buttons for Details screen
Function Init()
    ? "[DetailsScreen] init"

    m.top.observeField("visible", "onVisibleChange")
    m.top.observeField("focusedChild", "OnFocusedChildChange")

    m.buttons           =   m.top.findNode("Buttons")
    m.poster            =   m.top.findNode("Poster")
    m.description       =   m.top.findNode("Description")
    m.background        =   m.top.findNode("Background")


    ' create buttons
    result = []
    for each button in ["Play", "Second button"]
        result.push({title : button})
    end for
    m.buttons.content = ContentList2SimpleNode(result)
    m.top.content=m.buttons.content
End Function

' set proper focus to buttons if Details opened and stops Video if Details closed
Sub onVisibleChange()
    ? "[DetailsScreen] onVisibleChange"
    if m.top.visible = true then
        m.buttons.jumpToItem = 0
        m.buttons.setFocus(true)
    else if m.videoPlayer <> invalid
        m.videoPlayer.visible = false
        m.videoPlayer.control = "stop"
        m.poster.uri=""
        m.background.uri=""
    end if
End Sub

' set proper focus to Buttons in case if return from Video PLayer
Sub OnFocusedChildChange()
    if m.top.isInFocusChain() and not m.buttons.hasFocus() and not m.videoPlayer.hasFocus() then
        m.buttons.setFocus(true)
    end if
End Sub

' set proper focus on buttons and stops video if return from Playback to details
Sub onVideoVisibleChange()
    if m.videoPlayer.visible = false and m.top.visible = true
        m.buttons.setFocus(true)
        m.videoPlayer.control = "stop"
        'clear video player content, for proper start of next video player 
        m.videoPlayer.content = invalid
        'remove video player
        m.top.removeChild(m.videoPlayer)
    end if
End Sub

' event handler of Video player msg
Sub OnVideoPlayerStateChange()
    if m.videoPlayer.state = "error"
        ' error handling
        m.videoPlayer.visible = false
    else if m.videoPlayer.state = "playing"
        ' playback handling
        playContent()
    else if m.videoPlayer.state = "finished"
        m.videoPlayer.visible = false
    end if
End Sub



sub playContent()
    content = m.buttons.content
    if content <> invalid then
        m.video.content = content
        m.video.visible = false

        m.PlayerTask = CreateObject("roSGNode", "PlayerTask")
        m.PlayerTask.observeField("state", "taskStateChanged")
        m.PlayerTask.video = m.video
        m.PlayerTask.control = "RUN"
    end if
end sub

sub taskStateChanged(event as Object)
    print "Player: taskStateChanged(), id = "; event.getNode(); ", "; event.getField(); " = "; event.getData()
    state = event.GetData()
    if state = "done" or state = "stop"
        exitPlayer()
    end if
end sub


' on Button press handler
Sub onItemSelected()
    ' first button is Play
    if m.top.itemSelected = 0
        m.videoPlayer = CreateObject("roSGNode", "Video")
        m.videoPlayer.id="videoPlayer"
        m.videoPlayer.translation="[0, 0]"
        m.videoPlayer.width="1280"
        m.videoPlayer.height="720"
        m.videoPlayer.content   = m.top.content

        'show video player
        m.top.AppendChild(m.videoPlayer)

        m.videoPlayer.visible = true
        m.videoPlayer.setFocus(true)
        m.videoPlayer.control = "play"
        m.videoPlayer.observeField("state", "OnVideoPlayerStateChange")
        m.videoPlayer.observeField("visible", "onVideoVisibleChange")

    end if
End Sub

' Content change handler
Sub OnContentChange()
    m.description.content   = m.top.content
    m.description.Description.width = "770"
    m.poster.uri            = m.top.content.hdBackgroundImageUrl
    m.background.uri            = m.top.content.hdBackgroundImageUrl
End Sub

'///////////////////////////////////////////'
' Helper function convert AA to Node
Function ContentList2SimpleNode(contentList as Object, nodeType = "ContentNode" as String) as Object
    result = createObject("roSGNode",nodeType)
    if result <> invalid
        for each itemAA in contentList
            item = createObject("roSGNode", nodeType)
            item.setFields(itemAA)
            result.appendChild(item)
        end for
    end if
    return result
End Function

Function OnkeyEvent(key, press) as Boolean
    ? ">>> Details >> OnkeyEvent"
    result = false
    if press AND key = "back" AND m.videoPlayer <> invalid AND m.videoPlayer.visible 
        m.videoPlayer.visible = false
        result = true    
    end if
    return result
End Function

I added the "PlayContent" subroutine which points to the .brs file with the RAF integration. Added the TaskStateChanged event as I noticed it was missing. I am getting stuck at the section that has [m.PlayerTask.video = m.video]

Invalid value for left-side of expression. (runtime error &he4)

Can't seem to figure out where I can pass the values of the parsed XML document into the playertasks section to get it working. I'm understanding what I need to do just can't find easy enough documentation to get it.

'*********************************************************************
'** (c) 2016-2017 Roku, Inc.  All content herein is protected by U.S.
'** copyright and other applicable intellectual property laws and may
'** not be copied without the express permission of Roku, Inc., which
'** reserves all rights.  Reuse of any of this content for any purpose
'** without the permission of Roku, Inc. is strictly prohibited.
'********************************************************************* 

Library "Roku_Ads.brs"

sub init()
    m.top.functionName = "playContentWithAds" 
    m.top.id = "PlayerTask"
end sub

sub playContentWithAds()

    video = m.top.video
    ' `view` is the node under which RAF should display its UI (passed as 3rd argument of showAds())
    view = video.getParent() 

    RAF = Roku_Ads()
    'RAF.clearAdBufferScreenLayers()        ' in case it was set earlier
    'RAF.enableAdBufferMessaging(true, true) ' could have been cleared by custom screen
    'RAF.setAdBufferScreenContent({})

    content = video.content
    RAF.setAdUrl(content.ad_url)
    ' for generic measurements api
    RAF.setContentGenre(content.categories)  'if unset, ContentNode has it as []
    ' Nielsen DAR specific measurements
    if content.nielsen_app_id <> invalid:
        RAF.enableNielsenDAR(true)
        RAF.setNielsenAppId(content.nielsen_app_id)
        RAF.setNielsenGenre(content.nielsen_genre) 
        RAF.setNielsenProgramId(content.nielsen_program_id)
        RAF.setContentLength(content.length)
    end if

    ' log tracking events
'     logObj = {
'         log : Function(evtType = invalid as Dynamic, ctx = invalid as Dynamic)
'                   if GetInterface(evtType, "ifString") <> invalid
'                       print "*** tracking event " + evtType + " fired."
'                       if ctx.companion = true then
'                           print "***** companion = true"
'                       end if
'                       if ctx.errMsg <> invalid then print "*****   Error message: " + ctx.errMsg
'                       if ctx.adIndex <> invalid then print "*****  Ad Index: " + ctx.adIndex.ToStr()
'                       if ctx.ad <> invalid and ctx.ad.adTitle <> invalid then print "*****  Ad Title: " + ctx.ad.adTitle
'                   else if ctx <> invalid and ctx.time <> invalid
'                       print "*** checking tracking events for ad progress: " + ctx.time.ToStr()
'                   end if
'               End Function
'     }
'     logFunc = Function(obj = Invalid as Dynamic, evtType = invalid as Dynamic, ctx = invalid as Dynamic)
'                   obj.log(evtType, ctx)
'               End Function
'     RAF.setTrackingCallback(logFunc, logObj)

    adPods = RAF.getAds() 'array of ad pods
    keepPlaying = true 'gets set to `false` when showAds() was exited via Back button

    ' show the pre-roll ads, if any
    if adPods <> invalid and adPods.count() > 0
       keepPlaying = RAF.showAds(adPods, invalid, view)
    end if

    port = CreateObject("roMessagePort")
    if keepPlaying then
        video.observeField("position", port)
        video.observeField("state", port)
        video.visible = true
        video.control = "play"
        video.setFocus(true) 'so we can handle a Back key interruption
    end if

    curPos = 0
    adPods = invalid
    isPlayingPostroll = false
    while keepPlaying
        msg = wait(0, port)
        if type(msg) = "roSGNodeEvent"
            if msg.GetField() = "position" then
                ' keep track of where we reached in content
                curPos = msg.GetData() 
                ' check for mid-roll ads
                adPods = RAF.getAds(msg)
                if adPods <> invalid and adPods.count() > 0
                    print "PlayerTask: mid-roll ads, stopping video"
                    'ask the video to stop - the rest is handled in the state=stopped event below
                    video.control = "stop"  
                end if
            else if msg.GetField() = "state" then
                curState = msg.GetData()
                print "PlayerTask: state = "; curState
                if curState = "stopped" then
                    if adPods = invalid or adPods.count() = 0 then 
                        exit while
                    end if

                    print "PlayerTask: playing midroll/postroll ads"
                    keepPlaying = RAF.showAds(adPods, invalid, view)
                    adPods = invalid
                    if isPlayingPostroll then 
                        exit while
                    end if
                    if keepPlaying then
                        print "PlayerTask: mid-roll finished, seek to "; stri(curPos)
                        video.visible = true
                        video.seek = curPos
                        video.control = "play"
                        video.setFocus(true) 'important: take the focus back (RAF took it above)
                    end if

                else if curState = "finished" then
                    print "PlayerTask: main content finished"
                    ' render post-roll ads
                    adPods = RAF.getAds(msg)
                    if adPods = invalid or adPods.count() = 0 then 
                        exit while
                    end if
                    print "PlayerTask: has postroll ads"
                    isPlayingPostroll = true
                    ' stop the video, the post-roll would show when the state changes to  "stopped" (above)
                    video.control = "stop"
                end if
            end if
        end if
    end while

    print "PlayerTask: exiting playContentWithAds()"
end sub

Thank you everyone for helping me figure this out. Things have changed so much since when I focused on software development.