PlayerScreen manages live HLS stream playback using Roku’s built-in Video node. It layers an info banner, a channel number badge, and ad renderers on top of the video, and handles errors, reconnection, and network loss gracefully.
Video node
Roku’s native Video SceneGraph node plays HLS streams with streamFormat = "hls" and live = true
Channel banner
Info banner shows channel name, number, logo, and live status — auto-hides after 3 s
Number overlay
Digit-key input accumulates a channel number and tunes to it on commit
Watchdog timer
Freeze detection fires every 10 s; restarts the stream if position has not advanced
Playback is initiated by PlayChannel(idx), called either from onChannelIndexChanged() (when MainScene sets m.top.channelIndex) or from user navigation keys inside the player.
Copy
Ask AI
sub PlayChannel(idx as Integer) channels = m.global.channelList ch = channels[idx] m.currentIndex = idx m.retryCount = 0 ' Save resume position to Registry GTV_RegSaveLastChannelIndex(idx) m.global.currentChannelIndex = idx m.global.currentChannelId = ch.contentId playUrl = GTV_NormalizePlayerUrl(ch.url) content = CreateObject("roSGNode", "ContentNode") content.url = playUrl content.streamFormat = "hls" content.live = true m.video.content = content m.video.control = "play"end sub
The number overlay (m.numberOverlay) accumulates digit key presses. When the user stops pressing digits, numberCommitted fires and OnNumberCommitted() looks up the channel by number and calls PlayChannel:
Copy
Ask AI
sub OnNumberCommitted() committed = m.numberOverlay.numberCommitted target = Val(committed) idx = FindChannelIndexByNumber(target) if idx >= 0 PlayChannel(idx) else m.banner.statusText = "Canal " + committed + " no disponible" end ifend sub
When OnVideoError() fires and the error is not an auth/inactive signal, PlayerScreen retries up to RETRY_MAX = 3 times with a 2-second delay between attempts:
Copy
Ask AI
if m.retryCount < AppConstants().RETRY_MAX m.retryCount = m.retryCount + 1 retryTimer.duration = 2 retryTimer.control = "start"else ShowError(GTV_BuildStreamErrorMessage(errCode, errMsg))end if
After RETRY_MAX failures, an error dialog is shown with Retry and Back options.
GTV_IsAutoRecoverableStreamError classifies timeout, network, and HTTP response errors as auto-recoverable. When network connectivity is restored (via ConnectivityTask), any open error dialog of this type is automatically dismissed and the stream restarts.
GTV_IsInactiveStreamError inspects the error code and message for inactive-account signals (HTTP 401/403 with inactivo, inactive, subscriberDisabledReason, etc.). If detected:
Playback is stopped.
m.global.authReasonCode is set to AUTH_REASON_INACTIVE or AUTH_REASON_PASSWORD_CHANGED.
m.top.userInactive = true is set — MainScene handles the re-login flow.
The watchdog timer fires every FREEZE_CHECK_MS = 10000 ms while the video state is "playing". It compares the current playback position against the last recorded position. If they are equal (and the position is valid), the stream is considered frozen and RestartStream() is called:
Copy
Ask AI
sub OnWatchdog() state = m.video.state if state <> "playing" then return currentPos = m.video.position if currentPos = m.lastPosition and m.lastPosition >= 0 GTV_Warn("PlayerScreen", "Stream frozen at position " + currentPos.ToStr() + " - restarting") RestartStream() end if m.lastPosition = currentPosend sub
A netCheckTimer fires periodically during playback. It reads both GTV_IsOnline() (network interface up) and m.global.hasInternet (real internet reachability). Either failure puts the player into the offline state:
Video is stopped.
An offline dialog is shown with a Retry button.
ConnectivityTask is triggered for an immediate reachability probe.
When connectivity is restored, TryRecoverDialogsAfterNetworkRestore() hides the offline dialog and restarts the stream.
When AdManager activates a Format C ad, it emits videoHeightReduction and videoOffsetY fields. PlayerScreen observes these and adjusts the Video node’s geometry accordingly:
Copy
Ask AI
sub OnVideoHeightReduction() ApplyVideoViewportState() SyncAdViewport()end subsub ApplyVideoViewportState() reduction = m.adManager.videoHeightReduction offsetY = m.adManager.videoOffsetY newH = m.videoBaseHeight - reduction if newH < 480 then newH = 480 ' minimum video height m.video.translation = [m.videoBaseX, m.videoBaseY + offsetY] m.video.height = newHend sub
After adjusting the video, SyncAdViewport() pushes the updated viewport rect back to AdManager so ad renderers can reposition themselves relative to the live video area.If all ads are hidden (allAdsHidden event from AdManager), the video is restored to its base dimensions.