I am creating Roku channel. In my logic, there is an array of media(image&video), and I am displaying them with their timeout seconds. For example, one image is displaying 5 secs, then goes to next one. I have fading animation when going to next image. But I also need to add sliding animation. This is my code:
xml:
<component name="RendererScreen"
extends="Group">
<script type="text/brightscript"
uri="RendererScreen.bs" />
<children>
<Poster id="backgroundImg"
width="1920"
height="1080"
uri="pkg:/images/background.png" />
<Poster id="imgPoster"
width="1920"
height="1080"
translation="[0, 0]"
opacity="0.0"
visible="true" />
<Animation id="fadeInAnimation"
repeat="false"
control="stop"
easeFunction="linear">
<FloatFieldInterpolator id="fadeInInterpolator"
key="[0.0, 1.0]"
keyValue="[0.0, 1.0]"
fieldToInterp="imgPoster.opacity" />
</Animation>
<Animation id="fadeOutAnimation"
repeat="false"
control="stop"
easeFunction="linear">
<FloatFieldInterpolator id="fadeOutInterpolator"
key="[0.0, 1.0]"
keyValue="[1.0, 0.0]"
fieldToInterp="imgPoster.opacity" />
</Animation>
<Animation id="slideInAnimation"
repeat="false"
control="stop"
easeFunction="linear">
<FloatFieldInterpolator id="slideInInterpolator"
key="[0.0, 1.0]"
keyValue="[0.0, 1.0]"
fieldToInterp="imgPoster.translation" />
</Animation>
<Animation id="slideOutAnimation"
repeat="false"
control="stop"
easeFunction="linear">
<FloatFieldInterpolator id="slideOutInterpolator"
key="[0.0, 1.0]"
keyValue="[0.0, 1.0]"
fieldToInterp="imgPoster.translation" />
</Animation>
<Video id="videoPlayer"
width="1920"
height="1080"
translation="[0, 0]"
visible="false" />
</children>
</component>
bs file:
sub init()
m.poster = m.top.findNode("imgPoster")
m.backgroundImg = m.top.findNode("backgroundImg")
m.mediaList = []
m.currentIndex = 0
m.deviceOrientation = "90"
fetchPlaylistData()
end sub
sub showMedia()
if (m.mediaList.count() = 0)
return
end if
mediaItem = m.mediaList[m.currentIndex]
mediaType = mediaItem.type
mediaUrl = mediaItem.url
timeoutSeconds = mediaItem.timeoutSeconds
m.backgroundImg.visible = false
animationDuration = 1
fadeInAnimation = m.top.findNode("fadeInAnimation")
fadeOutAnimation = m.top.findNode("fadeOutAnimation")
fadeInAnimation.duration = animationDuration
fadeOutAnimation.duration = animationDuration
' I need to add here if clause(if animation is fade or slide)
if (mediaType = "image")
fadeInAnimation.control = "start"
displayImage(mediaUrl)
else if (mediaType = "video")
displayVideo(mediaUrl)
end if
startSlideShowTimer(timeoutSeconds)
end sub
sub displayVideo(url as string)
if (m.videoPlayer = invalid)
m.videoPlayer = m.top.findNode("videoPlayer")
m.videoPlayer.observeField("state", "onVideoStateChange")
end if
if (m.videoPlayer <> invalid)
videoContent = createObject("roSGNode", "ContentNode")
videoContent.url = url
m.videoPlayer.content = videoContent
m.videoPlayer.visible = true
m.videoPlayer.control = "play"
else
print "Error: Video node not found."
end if
end sub
sub onVideoStateChange()
if (m.videoPlayer.state = "error")
m.videoPlayer.control = "stop"
m.videoPlayer.visible = false
startErrorDelayTimer()
end if
end sub
sub displayImage(url as string)
if (m.poster <> invalid)
if(m.deviceOrientation = "90")
m.poster.width = "1080"
m.poster.height = "1920"
m.poster.translation = [420, -420]
m.poster.rotation = -1.570795 ' -> 3.14159/2 = π/2
else if(m.deviceOrientation = "180")
m.poster.width = "1920"
m.poster.height = "1080"
m.poster.translation = [0, 0]
m.poster.rotation = 3.14159 ' -> π
else if(m.deviceOrientation = "270")
m.poster.width = "1080"
m.poster.height = "1920"
m.poster.translation = [420, -420]
m.poster.rotation = 1.570795 ' -> 3.14159/2 = π/2
end if
m.poster.scaleRotateCenter = [m.poster.width / 2, m.poster.height / 2]
m.poster.uri = url
m.poster.observeField("loadStatus", "onPosterStateChange")
m.poster.visible = true
else
print "Error: Poster node not found."
end if
end sub
sub onPosterStateChange()
if (m.poster.loadStatus = "failed")
m.poster.visible = false
startErrorDelayTimer()
end if
end sub
sub startErrorDelayTimer()
if (m.errorDelayTimer = invalid)
m.errorDelayTimer = createObject("roSGNode", "Timer")
m.errorDelayTimer.observeField("fire", "onErrorDelayComplete")
m.top.appendChild(m.errorDelayTimer)
end if
m.errorDelayTimer.duration = 3
m.errorDelayTimer.repeat = false
m.errorDelayTimer.control = "start"
end sub
sub onErrorDelayComplete()
m.currentIndex = (m.currentIndex + 1) mod m.mediaList.count()
showMedia()
end sub
sub startSlideShowTimer(duration as integer)
if (m.timer = invalid)
m.timer = createObject("roSGNode", "Timer")
m.timer.observeField("fire", "onTimerFired")
m.top.appendChild(m.timer)
end if
m.timer.duration = duration
m.timer.repeat = false
m.timer.control = "start"
end sub
sub onTimerFired()
fadeOutAnimation = m.top.findNode("fadeOutAnimation")
fadeOutAnimation.observeField("completion", "onFadeOutComplete")
fadeOutAnimation.control = "start"
startOutTimer(fadeOutAnimation.duration)
end sub
sub startOutTimer(animationDuration as integer)
if (m.animationTimer = invalid)
m.animationTimer = createObject("roSGNode", "Timer")
m.animationTimer.observeField("fire", "onFadeOutComplete")
m.top.appendChild(m.animationTimer)
end if
m.animationTimer.duration = animationDuration
m.animationTimer.repeat = false
m.animationTimer.control = "start"
end sub
sub onFadeOutComplete()
if (m.videoPlayer <> invalid)
m.videoPlayer.control = "stop"
m.videoPlayer.visible = false
end if
m.currentIndex = (m.currentIndex + 1) mod m.mediaList.count()
showMedia()
end sub
To handle sliding in and out, you'll need to maintain two separate posters that you swap between. Here's an example showing how to fade between two posters. We leverage the .delay
property on animation to keep the current poster visible for the timeoutSeconds
in your example.
<?xml version="1.0" encoding="utf-8"?>
<component name="MainScene" extends="Scene">
<script type="text/brightscript" uri="MainScene.brs" />
<children>
<Poster id="posterPrimary" width="1920" height="1080" />
<Poster id="posterSecondary" width="1920" height="1080" />
<!--slide and fade the secondary poster INTO view and the primary poster OUT of view, -->
<Animation id="animationShowSecondary" repeat="false" control="stop" easeFunction="linear">
<FloatFieldInterpolator id="fadeInInterpolator1" key="[0.0, 1.0]" keyValue="[0.0, 1.0]" fieldToInterp="posterSecondary.opacity" />
<FloatFieldInterpolator id="fadeOutInterpolator1" key="[0.0, 1.0]" keyValue="[1.0, 0.0]" fieldToInterp="posterPrimary.opacity" />
<Vector2DFieldInterpolator id="slideInInterpolator1" key="[0.0, 1.0]" keyValue="[[1920,0], [0,0]]" fieldToInterp="posterSecondary.translation" />
<Vector2DFieldInterpolator id="slideOutInterpolator1" key="[0.0, 1.0]" keyValue="[[0,0], [-1920,0]]" fieldToInterp="posterPrimary.translation" />
</Animation>
<!--slide and fade the primary poster INTO view and the secondary poster OUT of view, -->
<Animation id="animationShowPrimary" repeat="false" control="stop" easeFunction="linear">
<FloatFieldInterpolator id="fadeInInterpolator2" key="[0.0, 1.0]" keyValue="[0.0, 1.0]" fieldToInterp="posterPrimary.opacity" />
<FloatFieldInterpolator id="fadeOutInterpolator2" key="[0.0, 1.0]" keyValue="[1.0, 0.0]" fieldToInterp="posterSecondary.opacity" />
<Vector2DFieldInterpolator id="slideInInterpolator2" key="[0.0, 1.0]" keyValue="[[1920,0], [0,0]]" fieldToInterp="posterPrimary.translation" />
<Vector2DFieldInterpolator id="slideOutInterpolator2" key="[0.0, 1.0]" keyValue="[[0,0], [-1920,0]]" fieldToInterp="posterSecondary.translation" />
</Animation>
</children>
</component>
sub init()
m.posterPrimary = m.top.findNode("posterPrimary")
m.posterSecondary = m.top.findNode("posterSecondary")
m.animationShowSecondary = m.top.findNode("animationShowSecondary")
m.animationShowPrimary = m.top.findNode("animationShowPrimary")
'anytime the animations change, we want to know about it
m.animationShowSecondary.observeFieldScoped("state", "onanimationShowSecondaryStateChange")
m.animationShowPrimary.observeFieldScoped("state", "onanimationShowPrimaryStateChange")
'list of media items. This can be fetched dynamically from the server, and each one specifies how long they are visible
m.mediaItems = [{
uri: "pkg:/media/water.jpg",
timeoutSeconds: 3
}, {
uri: "pkg:/media/bunny.jpg",
timeoutSeconds: 5
}]
'show the first poster
showImageAtIndex(m.posterSecondary, 1)
showImageAtIndex(m.posterPrimary, 0)
m.animationShowSecondary.delay = m.mediaItems[m.currentMediaItemIndex].timeoutSeconds
m.animationShowSecondary.control = "start"
end sub
function showImageAtIndex(poster, mediaItemIndex as integer)
if mediaItemIndex > m.mediaItems.count() - 1
mediaItemIndex = 0
end if
mediaItem = m.mediaItems[mediaItemIndex]
poster.uri = mediaItem.uri
m.currentMediaItemIndex = mediaItemIndex
end function
sub onanimationShowSecondaryStateChange()
if m.animationShowSecondary.state = "stopped"
showImageAtIndex(m.posterPrimary, m.currentMediaItemIndex + 1)
m.animationShowPrimary.delay = m.mediaItems[m.currentMediaItemIndex].timeoutSeconds
m.animationShowPrimary.control = "start"
end if
end sub
sub onanimationShowPrimaryStateChange()
if m.animationShowPrimary.state = "stopped"
showImageAtIndex(m.posterSecondary, m.currentMediaItemIndex + 1)
m.animationShowSecondary.delay = m.mediaItems[m.currentMediaItemIndex].timeoutSeconds
m.animationShowSecondary.control = "start"
end if
end sub