Skip to main content
This page traces every step the application takes from the moment the Roku OS launches the channel to the point where live video is playing and ads are polling.

Launch sequence

1

main.brs entry point

The Roku OS calls Main(args) in source/main.brs. The function:
  1. Creates a roSGScreen and a shared roMessagePort.
  2. Creates a roInput object on the same port to receive warm-start deep links.
  3. Creates a roDeviceInfo and roAppMemoryMonitor and enables memory warning events.
  4. Calls screen.CreateScene("MainScene") — this is the single instantiation of the root scene.
  5. Observes exitRequested on the scene so the event loop can close the screen.
  6. Calls screen.Show() to start rendering.
  7. Checks the launch args for a valid live deep link before entering the event loop.
sub Main(args as Dynamic)
    screen = CreateObject("roSGScreen")
    port   = CreateObject("roMessagePort")
    screen.SetMessagePort(port)

    input = CreateObject("roInput")
    input.SetMessagePort(port)

    scene = screen.CreateScene("MainScene")
    scene.ObserveField("exitRequested", port)
    screen.Show()

    if args <> invalid
        if GTV_IsLiveDeepLink(args)
            scene.launchDeepLink = args
        end if
    end if
    ' ... event loop
end sub
Deep link validation requires both contentId (non-empty string) and mediaType = "live" (case-insensitive). Any other mediaType is silently ignored.
2

MainScene initialisation

MainScene.init() in components/MainScene.brs runs synchronously on the render thread:
  1. Calls GTV_InitGlobalState() — adds all m.global fields with their default values.
  2. Calls ApplySceneLayout() — sizes the background Rectangle to fill the screen.
  3. Creates a relayout timer (100 ms, one-shot) and a focus-repair timer (50 ms, one-shot).
  4. Creates a session-auth timer (SESSION_AUTH_CHECK_MS = 420 s, repeating) for periodic credential re-validation.
  5. Initialises all screen references to invalid and overlay state to "player_only".
  6. Calls ShowSplash().
3

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().
4

Auth decision: auto-login vs onboarding

Auto-login pathRunAutoLogin() 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 pathShowOnboarding() 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():
CodeMeaningAction
460Network downShow offline error
401Wrong credentialsRe-prompt login
470Account inactiveShow inactivity dialog with retry
471Password changedClear credentials, force re-login
5

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.
6

Playlist loading

OnAuthSuccess() starts the session-auth timer and calls RunPlaylistTask("startup"). The task:
  1. Downloads GET /auth/{user}/{pass}/playlist/m3u8/hls.
  2. Parses the M3U8 file via M3UParser.brs into a flat channels array and a categories map.
  3. Writes results to m.global.channelList and m.global.categories.
  4. Sets done = true on 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.
7

MainScreen display

ShowPlayer(channelIndex) always ensures MainScreen exists before creating PlayerScreen. If m.mainScreen is invalid, ShowMainScreen() is called first:
  • Creates a MainScreen child node.
  • Sets channels, categories, and initialIndex fields.
  • Observes channelSelected, openSettings, closeOverlay, and requestExit.
When MainScreen.onChannelsChanged() fires it calls SignalAppLaunchCompleteOnce():
sub SignalAppLaunchCompleteOnce()
    scene = m.top.GetScene()
    if scene <> invalid
        if m.global.appLaunchBeaconSent <> true
            scene.signalBeacon("AppLaunchComplete")
            m.global.appLaunchBeaconSent = true
        end if
    end if
end sub
This satisfies the Roku certification requirement that AppLaunchComplete is sent exactly once, after content is ready to display.
8

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:
  1. Finds the Video node (videoPlayer) and calls ApplyResponsiveLayout() to set its viewport.
  2. Spawns ConnectivityTask, AdsPollingTask, and MetricsTask.
  3. Observes m.adManager.videoHeightReduction and videoOffsetY for Format C ad viewport compression.
  4. Calls PlayChannel(channelIndex) via the channelIndex field 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.
9

Ad polling

AdsPollingTask runs continuously while PlayerScreen is active:
  1. Handshake — POST to ADS_PATH_HANDSHAKE (/app/devices/handshake) with device identity.
  2. Poll loop — GET ADS_PATH_ACTIVE (/app/ads/active) every ADS_POLL_MS (10 000 ms).
  3. On each response, the task sets adsSnapshot which triggers OnAdsSnapshot() in PlayerScreen.
  4. PlayerScreen matches the snapshot to the current channel via GTV_IsSnapshotForCurrentChannel() and forwards it to AdManager.
  5. AdManager renders the appropriate format. If Format C is active it emits a videoHeightReduction value that shrinks the Video node 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(). GlobalTV supports mediaType=live deep links at two points in the lifecycle: Launch deep link (cold start) — detected in main.brs before the event loop:
if args <> invalid
    if GTV_IsLiveDeepLink(args)
        scene.launchDeepLink = args
    end if
end if
Setting 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:
else if type(msg) = "roInputEvent"
    info = msg.GetInfo()
    if info <> invalid
        if GTV_IsLiveDeepLink(info)
            scene.inputDeepLink = info
        end if
    end if
Setting 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.
The session timer is stopped whenever a handshake, auth, or playlist task is already in flight to avoid overlapping requests.

Exit flow

Any screen can set requestExit 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().