Launch sequence
main.brs entry point
The Roku OS calls Deep link validation requires both
Main(args) in source/main.brs. The function:- Creates a
roSGScreenand a sharedroMessagePort. - Creates a
roInputobject on the same port to receive warm-start deep links. - Creates a
roDeviceInfoandroAppMemoryMonitorand enables memory warning events. - Calls
screen.CreateScene("MainScene")— this is the single instantiation of the root scene. - Observes
exitRequestedon the scene so the event loop can close the screen. - Calls
screen.Show()to start rendering. - Checks the launch
argsfor a valid live deep link before entering the event loop.
contentId (non-empty string) and mediaType = "live" (case-insensitive). Any other mediaType is silently ignored.MainScene initialisation
MainScene.init() in components/MainScene.brs runs synchronously on the render thread:- Calls
GTV_InitGlobalState()— adds allm.globalfields with their default values. - Calls
ApplySceneLayout()— sizes the backgroundRectangleto fill the screen. - Creates a relayout timer (100 ms, one-shot) and a focus-repair timer (50 ms, one-shot).
- Creates a session-auth timer (
SESSION_AUTH_CHECK_MS= 420 s, repeating) for periodic credential re-validation. - Initialises all screen references to
invalidand overlay state to"player_only". - Calls
ShowSplash().
Splash screen
ShowSplash() creates a SplashScreen child node. When the splash timer fires, the component sets splashDone = true, which triggers OnSplashDone() in MainScene.OnSplashDone() checks the registry for saved credentials:- Credentials found → calls
RunAutoLogin(username, password). - No credentials → calls
ShowOnboarding().
Auth decision: auto-login vs onboarding
Auto-login path —
RunAutoLogin() creates an AuthTask with the saved username and password and shows a loading label. The task authenticates against /auth/{user}/{pass} with a TIMEOUT_AUTH (15 s) timeout. On completion OnAuthDone() handles the result.Onboarding path — ShowOnboarding() creates an OnboardingScreen. When the user presses the continue button, OnGoToLogin() → ShowLogin() creates a LoginScreen.LoginScreen first calls ChannelStore.getUserData with requestedUserData = "email" to obtain the Roku account email (RFI — Roku First-party Identity). Once the RFI resolves (or times out), the user enters credentials via StandardKeyboardDialog and AuthTask runs.Auth failure codes are classified by GTV_AuthClassifyFailure():| Code | Meaning | Action |
|---|---|---|
| 460 | Network down | Show offline error |
| 401 | Wrong credentials | Re-prompt login |
| 470 | Account inactive | Show inactivity dialog with retry |
| 471 | Password changed | Clear credentials, force re-login |
Handshake
Before the first login attempt (when no credentials exist),
RunHandshake() creates a HandshakeTask. The task iterates the server list from AppConstants().SERVER_LIST, calling GET /health on each with a TIMEOUT_HEALTH (2500 ms) timeout and up to SERVER_LAN_FORCE_RETRIES (3) retries on LAN addresses.The first server that responds successfully is written to m.global.activeServer. On OnHandshakeDone(), MainScene proceeds to the appropriate next step based on the handshakeContext field ("autologin" or "onboarding").Handshake is skipped when auto-login runs from saved credentials — the active server is either read from the registry (
lastServer) or resolved lazily by AuthTask itself.Playlist loading
OnAuthSuccess() starts the session-auth timer and calls RunPlaylistTask("startup"). The task:- Downloads
GET /auth/{user}/{pass}/playlist/m3u8/hls. - Parses the M3U8 file via
M3UParser.brsinto a flatchannelsarray and acategoriesmap. - Writes results to
m.global.channelListandm.global.categories. - Sets
done = trueon the task node.
OnPlaylistDone() in MainScene handles the result. If a launch deep link was queued in m.pendingDeepLink, it resolves the channel index now and calls ShowPlayer(idx) directly. Otherwise it calls ResolveStartupChannelIndex() which honours the lastChannelIndex registry value.MainScreen display
ShowPlayer(channelIndex) always ensures MainScreen exists before creating PlayerScreen. If m.mainScreen is invalid, ShowMainScreen() is called first:- Creates a
MainScreenchild node. - Sets
channels,categories, andinitialIndexfields. - Observes
channelSelected,openSettings,closeOverlay, andrequestExit.
MainScreen.onChannelsChanged() fires it calls SignalAppLaunchCompleteOnce():AppLaunchComplete is sent exactly once, after content is ready to display.PlayerScreen and live playback
ShowPlayer() creates a PlayerScreen node inserted below MainScreen in the scene tree (so MainScreen can render on top as an overlay when needed). On init, PlayerScreen:- Finds the
Videonode (videoPlayer) and callsApplyResponsiveLayout()to set its viewport. - Spawns
ConnectivityTask,AdsPollingTask, andMetricsTask. - Observes
m.adManager.videoHeightReductionandvideoOffsetYfor Format C ad viewport compression. - Calls
PlayChannel(channelIndex)via thechannelIndexfield observer.
PlayChannel() builds a ContentNode with url, streamFormat = "hls", and live = true, then sets m.video.control = "play". The watchdog timer (FREEZE_CHECK_MS = 10 s) monitors m.video.position to detect frozen streams and restarts them automatically.Ad polling
AdsPollingTask runs continuously while PlayerScreen is active:- Handshake — POST to
ADS_PATH_HANDSHAKE(/app/devices/handshake) with device identity. - Poll loop — GET
ADS_PATH_ACTIVE(/app/ads/active) everyADS_POLL_MS(10 000 ms). - On each response, the task sets
adsSnapshotwhich triggersOnAdsSnapshot()in PlayerScreen. - PlayerScreen matches the snapshot to the current channel via
GTV_IsSnapshotForCurrentChannel()and forwards it toAdManager. AdManagerrenders the appropriate format. If Format C is active it emits avideoHeightReductionvalue that shrinks theVideonode height.
Overlay flow
When the user presses left during playback,OnRequestOverlay() makes MainScreen visible with overlayMode = true. Key events route through PlayerScreen.RouteOverlayKey() → MainScreen.HandleOverlayKey(). Pressing OK on a channel commits channelSelected, which triggers OnChannelSelected() in MainScene and calls m.playerScreen.channelIndex = idx.
Pressing Back or selecting the same channel dismisses the overlay via OnCloseOverlay() → HideOverlayState().
Deep link handling
GlobalTV supportsmediaType=live deep links at two points in the lifecycle:
Launch deep link (cold start) — detected in main.brs before the event loop:
scene.launchDeepLink triggers onLaunchDeepLink() in MainScene (via onChange), which stores the link in m.pendingDeepLink. The link is consumed in OnPlaylistDone() once channels are available.
Input deep link (warm start) — received as roInputEvent in the main.brs event loop:
scene.inputDeepLink triggers onInputDeepLink() → ProcessDeepLink(), which resolves the contentId to a channel index and calls ShowPlayer(idx) immediately if channels are loaded, or queues it in m.pendingDeepLink otherwise.
Session auth re-validation
After a successful login, MainScene starts a repeating timer (SESSION_AUTH_CHECK_MS = 420 s ≈ 7 minutes). On each fire, OnSessionAuthTimer() creates a new AuthTask with the saved credentials. If the task fails with a classified reason code, MainScene shows the appropriate session-issue dialog and stops playback.
Exit flow
Any screen can setrequestExit to trigger OnRequestExit(), which creates a ConfirmExitDialog. On confirmation, m.top.exitRequested is incremented, which the main.brs event loop detects via roSGNodeEvent and calls screen.Close().