-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
dannykng
committed
Mar 29, 2017
1 parent
27bb171
commit 05071d3
Showing
14 changed files
with
389 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,23 @@ | ||
# RAF4RSG-sample | ||
sample demonstrating the Roku Advertising Framework in SceneGraph | ||
# RAF4RSG Sample | ||
|
||
This sample code demonstrates the use of Roku Advertising Framework in a Roku SceneGraph app. With v2 of RAF, the preferred way of use is by invoking the library from a Task node and passing a 3rd argument (`view`) to .ShowAds, which specifies where exactly in scene graph the ad content (video and interactive ad UI) should be attached. | ||
|
||
## How does it work | ||
|
||
main() creates an EntryScene (extends Scene), which only has a simplistic menu (LabelList). Selecting from there configures a VideoContent node with the video and ad information and starts playing it via a Player child node. | ||
|
||
Player (extends Group) in turn creates a Video node child, sets the main content in it and delegates running the video and ad play to a PlayerTask. | ||
|
||
PlayerTask (extends Task) in this sample is the only one "tasked" (pun intended) with playing the main content and the corresponding ads. It is also the only one that has to include the RAF library (`Library "Roku_Ads.brs"`). The runner function in PlayerTask - playContentWithAds() - makes the necessary library calls, the most notable being `.getAds()` to fetch ad pods and `.showAds()` to display them. Note that in RAF2 .showAds() has now a new, 3rd parameter - `view`, that is used to provide a renderable node in Roku Scene Graph, under which RAF should temporary attach its UI (incl. an ad video player node). | ||
|
||
PlayContentWithAds() has an event loop, in which it keeps track of `position` events during playback of the main content, so that it could interrupt and insert mid-roll ads as appropriate (determined by repeat calls to .getAds()) - and when that is done, it resumes the main video. The event loop also handles other player state changes besides "position", for example it latches onto the video "finished" event for a chance to display post-roll ads. | ||
|
||
## Pre-roll, mid-roll and post-roll ads | ||
|
||
The code can handle pre-roll, mid-roll and post-roll ads on the same video content - subject to the ad server returning the pods configured that way during the .getAds() call. | ||
|
||
Note that if invoked without a custom URL (as the sample does), only a single pre-roll ad will be shown, because that is the Roku ad server's default configuration. To change that behavior, .setAdUrl() pointing to customer's ad server has to be used (this falls under the "inventory split" model, see "Revenue Share" section in the documentation). | ||
|
||
## Caveats | ||
|
||
Notice this wrinkle - of PlayerTask having to manually set back the input focus after playing ads, since RAF UI has had the focus to be able to handle remote button presses. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
'********************************************************************* | ||
'** (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. | ||
'********************************************************************* | ||
|
||
sub init() | ||
'we use a simple LabelList for a menu | ||
m.list = m.top.FindNode("list") | ||
m.list.observeField("itemSelected", "onItemSelected") | ||
m.list.SetFocus(true) | ||
|
||
'descriptor for the menu items | ||
itemList = [ | ||
{ | ||
title: "Roku ad server (default server URL, single pre-roll)" | ||
url: "" 'point to your own ad server if doing "inventory split" revenue share | ||
} | ||
] | ||
|
||
' compile into a ContentNode structure | ||
listNode = CreateObject("roSGNode", "ContentNode") | ||
for each item in itemList: | ||
nod = CreateObject("roSGNode", "ContentNode") | ||
nod.setFields(item) | ||
listNode.appendChild(nod) | ||
next | ||
m.list.content = listNode | ||
|
||
end sub | ||
|
||
sub onItemSelected() | ||
m.list.SetFocus(false) ' un-set focus to avoid creating multiple players on user tapping twice | ||
menuItem = m.list.content.getChild(m.list.itemSelected) | ||
|
||
videoContent = { | ||
|
||
streamFormat: "hls", | ||
titleSeason: "Art21 Season 3", | ||
title: "Place", | ||
url: "http://ga.video.cdn.pbs.org/videos/art-21/fe42d468-e90f-489b-ade3-a242f196dba2/60600/hd-mezzanine-16x9/ARTF101_iPad-1240k.m3u8", | ||
|
||
'used for raf.setContentGenre(). For ads provided by the Roku ad service, see docs on 'Roku Genre Tags' | ||
categories: ["Documentary"] | ||
|
||
'Roku mandates that all channels enable Nielsen DAR | ||
nielsen_app_id: "P2871BBFF-1A28-44AA-AF68-C7DE4B148C32" 'required, put "P2871BBFF-1A28-44AA-AF68-C7DE4B148C32", Roku's default appId if not having ID from Nielsen | ||
nielsen_genre: "DO" 'required, put "GV" if dynamic genre or special circumstances (e.g. games) | ||
nielsen_program_id: "Art21" 'movie title or series name | ||
length: 3220 'in seconds; | ||
|
||
} | ||
' compile into a VideoContent node | ||
content = CreateObject("roSGNode", "VideoContent") | ||
content.setFields(videoContent) | ||
content.ad_url = menuItem.url | ||
|
||
if m.Player = invalid: | ||
m.Player = m.top.CreateChild("Player") | ||
m.Player.observeField("state", "PlayerStateChanged") | ||
end if | ||
|
||
'start the player | ||
m.Player.content = content | ||
m.Player.visible = true | ||
m.Player.control = "play" | ||
end sub | ||
|
||
sub PlayerStateChanged() | ||
print "EntryScene: PlayerStateChanged(), state = "; m.Player.state | ||
if m.Player.state = "done" or m.Player.state = "stop" | ||
m.Player.visible = false | ||
m.list.setFocus(true) 'NB. the player took the focus away, so get it back | ||
end if | ||
end sub | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
|
||
<component name="EntryScene" extends="Scene"> | ||
|
||
<script type="text/brightscript" uri="pkg:/components/EntryScene.brs" /> | ||
|
||
<children> | ||
<LabelList id="list" itemSize="[800, 50]" translation="[100,50]" /> | ||
</children> | ||
|
||
</component> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
'********************************************************************* | ||
'** (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. | ||
'********************************************************************* | ||
|
||
' Player | ||
|
||
sub init() | ||
m.video = m.top.CreateChild("Video") | ||
end sub | ||
|
||
sub controlChanged() | ||
'handle orders by the parent/owner | ||
control = m.top.control | ||
if control = "play" then | ||
playContent() | ||
else if control = "stop" then | ||
exitPlayer() | ||
end if | ||
end sub | ||
|
||
sub playContent() | ||
content = m.top.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 exitPlayer() | ||
print "Player: exitPlayer()" | ||
m.video.control = "stop" | ||
m.video.visible = false | ||
m.PlayerTask = invalid | ||
|
||
'signal upwards that we are done | ||
m.top.state = "done" | ||
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 | ||
|
||
function onKeyEvent(key as String, press as Boolean) as Boolean | ||
' since m.video is a child of `player` in the scene graph, | ||
' pressing the Back button during play will "bubble up" for us to handle here | ||
print "Player: keyevent = "; key | ||
|
||
if press and key = "back" then | ||
'handle Back button, by exiting play | ||
exitPlayer() | ||
return true | ||
end if | ||
return false | ||
end function | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
|
||
<!-- RAF player for playing ad supported content. --> | ||
<component name="Player" extends="Group"> | ||
<interface> | ||
<!-- video content descriptor, ContentNode-based; includes ad_url if non-default --> | ||
<field id="content" type="node" /> | ||
|
||
<!-- state - Same as Task states. --> | ||
<field id="state" type="string" alwaysNotify="true"/> | ||
|
||
<!-- control - "play" starts content playback --> | ||
<field id="control" type="string" alwaysNotify="true" onChange="controlChanged"/> | ||
</interface> | ||
|
||
<script type="text/brightscript" uri="pkg:/components/Player.brs" /> | ||
</component> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
'********************************************************************* | ||
'** (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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?rokuml version="1.0" encoding="utf-8" ?> | ||
|
||
<!-- a Task handling the playback loop of both content abd ad --> | ||
<component name="PlayerTask" extends="Task"> | ||
|
||
<script type="text/brightscript" uri="pkg:/components/PlayerTask.brs" /> | ||
|
||
<interface> | ||
<field id="video" type="node" /> | ||
</interface> | ||
|
||
</component> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<component name = "VideoContent" extends = "ContentNode" > | ||
|
||
<!-- expanding ContentNode with additional fields, so can set them all with a setFields() --> | ||
<interface> | ||
<field id = "ad_url" type = "string" /> | ||
<field id = "nielsen_app_id" type = "string" /> | ||
<field id = "nielsen_genre" type = "string" /> | ||
<field id = "nielsen_program_id" type = "string" /> | ||
</interface> | ||
|
||
</component> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
title=Sample use of RAF in RSG | ||
major_version=1 | ||
minor_version=1 | ||
build_version=1 | ||
bs_libs_required=roku_ads_lib | ||
ui_resolutions=hd | ||
mm_icon_focus_hd=pkg:/images/MainMenu_Icon_Center_HD.png | ||
mm_icon_focus_sd=pkg:/images/MainMenu_Icon_Center_SD43.png | ||
splash_screen_hd=pkg:/images/splash_hd.jpg | ||
splash_screen_sd=pkg:/images/splash_sd.jpg | ||
splash_color=#404040 |
Oops, something went wrong.