> ## Documentation Index
> Fetch the complete documentation index at: https://globaliptv.misidev.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tasks

> Reference for all SceneGraph Task nodes in the GlobalTV Roku channel. Tasks run on a separate thread and communicate with the render thread via observed fields.

All tasks extend the SceneGraph `Task` node. They are created with `CreateObject("roSGNode", "<TaskName>")`, have their input fields set, and are started with `task.control = "RUN"`. Completion is signaled by setting a `done` field or — for long-running tasks — by writing output fields that the caller observes.

<AccordionGroup>
  <Accordion title="AuthTask">
    **File:** `components/tasks/AuthTask.*`\
    **Function:** `RunAuth`

    ### Purpose

    Authenticates a username/password pair against the backend. Resolves the active server first (LAN probe → WAN fallback, up to `SERVER_LAN_FORCE_RETRIES = 3` LAN attempts), then performs a GET to `/auth/{user}/{pass}` with `TIMEOUT_AUTH = 15 000 ms`.

    ### XML interface

    ```xml theme={null}
    <component name="AuthTask" extends="Task">
      <interface>
        <!-- inputs -->
        <field id="username"      type="string"    value="" />
        <field id="password"      type="string"    value="" />
        <!-- outputs -->
        <field id="result"        type="assocarray" />
        <field id="done"          type="boolean"   value="false" />
        <field id="statusMessage" type="string"    value="" />
        <field id="statusTick"    type="integer"   value="0" />
      </interface>
    </component>
    ```

    ### Input fields

    | Field      | Type   | Description          |
    | ---------- | ------ | -------------------- |
    | `username` | string | Plain-text username. |
    | `password` | string | Plain-text password. |

    ### Output fields

    | Field           | Type       | Description                                                                    |
    | --------------- | ---------- | ------------------------------------------------------------------------------ |
    | `result`        | assocarray | Full auth result (see structure below).                                        |
    | `done`          | boolean    | Set to `true` when the task finishes (success or failure).                     |
    | `statusMessage` | string     | Human-readable progress text (e.g. "Conectando con servidor local...").        |
    | `statusTick`    | integer    | Incremented each time `statusMessage` changes; observe this to detect updates. |

    ### Result structure

    ```brightscript theme={null}
    result = {
        success                  : false,
        userId                   : "",
        streamUrl                : "",
        subscriberActive         : false,
        subscriberDisabledReason : "",
        errorCode                : -1,      ' AUTH_REASON_* constant
        authReason               : 0,
        errorMsg                 : "",
        rawBody                  : "",
        resolvedServer           : ""
    }
    ```

    ### Completion signal

    Observe `done`. When `done = true`, read `result`. `result.success = true` means authentication passed; `result.subscriberActive` confirms the subscriber account is active.

    ### Auth reason codes

    | Constant                       | Value | Meaning                              |
    | ------------------------------ | ----- | ------------------------------------ |
    | `AUTH_REASON_NONE`             | 0     | No error.                            |
    | `AUTH_REASON_CREDENTIALS`      | 401   | Wrong username or password.          |
    | `AUTH_REASON_INACTIVE`         | 470   | Account is inactive.                 |
    | `AUTH_REASON_PASSWORD_CHANGED` | 471   | Password was changed; must re-login. |
    | `AUTH_REASON_NETWORK_DOWN`     | 460   | Network or server unreachable.       |
  </Accordion>

  <Accordion title="PlaylistTask">
    **File:** `components/tasks/PlaylistTask.*`\
    **Function:** `RunPlaylist`

    ### Purpose

    Downloads the authenticated M3U8 playlist from `/auth/{user}/{pass}/playlist/m3u8/hls` and parses it into channel and category arrays. Supports server failover: up to `RETRY_MAX = 3` attempts across the server sequence.

    ### XML interface

    ```xml theme={null}
    <component name="PlaylistTask" extends="Task">
      <interface>
        <!-- inputs -->
        <field id="username" type="string" value="" />
        <field id="password" type="string" value="" />
        <!-- outputs -->
        <field id="result"   type="assocarray" />
        <field id="done"     type="boolean"   value="false" />
      </interface>
    </component>
    ```

    ### Input fields

    | Field      | Type   | Description                      |
    | ---------- | ------ | -------------------------------- |
    | `username` | string | Credential for the playlist URL. |
    | `password` | string | Credential for the playlist URL. |

    ### Output fields

    | Field    | Type       | Description                                 |
    | -------- | ---------- | ------------------------------------------- |
    | `result` | assocarray | Full playlist result (see structure below). |
    | `done`   | boolean    | Set to `true` on completion.                |

    ### Result structure

    ```brightscript theme={null}
    result = {
        success    : false,
        channels   : [],      ' array of channel objects
        categories : [],      ' array of category name strings
        errorMsg   : "",
        errorCode  : 0,       ' AUTH_REASON_* constant
        authReason : 0,
        rawBody    : "",
        httpCode   : 0
    }
    ```

    Each entry in `result.channels` is a channel object:

    ```brightscript theme={null}
    {
        id        : "",      ' tvg-id
        name      : "",      ' tvg-name (or from #EXTINF trailing label)
        logo      : "",      ' tvg-logo URL
        group     : "General",
        number    : -1,       ' channel-number attribute
        url       : "",       ' HLS stream URL
        streamId  : "",       ' extracted from URL path
        contentId : ""        ' streamId if found, else channel number string
    }
    ```

    ### Completion signal

    Observe `done`. On success, `result.channels` and `result.categories` are populated and also written directly to `m.global.channelList` / `m.global.categories`.

    ### Logging

    The task logs `extinf_sample` lines (first 3 `#EXTINF` entries), a `logo_summary`, and up to 5 `channel_sample` lines at the `GTV_Log` level.
  </Accordion>

  <Accordion title="AdsPollingTask">
    **File:** `components/tasks/AdsPollingTask.*`\
    **Function:** `RunAdsPolling`

    ### Purpose

    Long-running polling task. Performs the initial ads handshake (if `ADS_HANDSHAKE_ENABLED = true`), then polls the active snapshot endpoint every `ADS_POLL_MS = 10 000 ms`. Tracks impression versioning and signals session-level auth failures back to `MainScene`.

    ### XML interface

    ```xml theme={null}
    <component name="AdsPollingTask" extends="Task">
      <interface>
        <!-- inputs -->
        <field id="streamId"            type="string"  value="" />
        <field id="streamPosition"      type="string"  value="" />
        <field id="resetChannel"        type="boolean" value="false" onChange="onResetChannel" />
        <!-- ad output -->
        <field id="adData"              type="assocarray" />
        <field id="adsSnapshot"         type="assocarray" />
        <!-- clear signal -->
        <field id="adsCleared"          type="boolean" value="false" />
        <field id="clearedStreamPosition" type="string" value="" />
        <field id="clearedStreamId"     type="string" value="" />
        <!-- session signals -->
        <field id="userInactive"        type="boolean" value="false" />
        <field id="userInactiveReason"  type="string"  value="" />
        <field id="sessionInvalid"      type="boolean" value="false" />
        <field id="sessionInvalidReason" type="string" value="" />
      </interface>
    </component>
    ```

    ### Input fields

    | Field            | Type    | Description                                                                                   |
    | ---------------- | ------- | --------------------------------------------------------------------------------------------- |
    | `streamId`       | string  | Current stream identifier (from channel `streamId`).                                          |
    | `streamPosition` | string  | Channel number string; fallback stream identifier.                                            |
    | `resetChannel`   | boolean | Set to `true` when the channel changes; resets `sinceVersion` and triggers an immediate poll. |

    ### Output fields

    | Field                   | Type       | Description                                                         |
    | ----------------------- | ---------- | ------------------------------------------------------------------- |
    | `adData`                | assocarray | Normalized first ad from the latest snapshot.                       |
    | `adsSnapshot`           | assocarray | Full snapshot object (version, ads array, stream info).             |
    | `adsCleared`            | boolean    | Toggled `false → true` when the server signals no active ads.       |
    | `clearedStreamPosition` | string     | Stream position from the clear event.                               |
    | `clearedStreamId`       | string     | Stream ID from the clear event.                                     |
    | `userInactive`          | boolean    | Toggled when the polling response indicates account inactivity.     |
    | `userInactiveReason`    | string     | Localized reason string for the inactive state.                     |
    | `sessionInvalid`        | boolean    | Toggled when the response signals credential/password invalidation. |
    | `sessionInvalidReason`  | string     | Localized reason string.                                            |

    ### Completion signal

    This is a long-running task; it does **not** use a `done` field. Stop it by setting `control = "stop"`.

    ### Poll endpoint

    Requests are sent to `ADS_API_BASE_URL + ADS_PATH_ACTIVE` (default: `https://ads.globaltv.lat/api/v1/app/ads/active`) with query parameters:

    ```
    ?device_id=<id>&stream_id=<id>&since_version=<ver>
    ```

    or, if `stream_id` is unavailable:

    ```
    ?device_id=<id>&stream_position=<number>&since_version=<ver>
    ```

    ### Handshake

    Before the first poll, `TryAdsHandshake()` POSTs to `ADS_PATH_HANDSHAKE` with device and subscriber info. Failures are logged but do not block polling.
  </Accordion>

  <Accordion title="ConnectivityTask">
    **File:** `components/tasks/ConnectivityTask.*`\
    **Function:** `RunConnectivityTask`

    ### Purpose

    Periodic network health checker. Pings the active server's health endpoint every 10 seconds and updates `m.global.hasInternet` / `m.global.isOnline`. Suppresses log spam by only logging on state transitions.

    ### XML interface

    ```xml theme={null}
    <component name="ConnectivityTask" extends="Task">
      <interface>
        <!-- output -->
        <field id="hasInternet" type="boolean" value="true" />
        <!-- trigger -->
        <field id="checkNow"    type="boolean" value="false" />
      </interface>
    </component>
    ```

    ### Fields

    | Field         | Direction | Type    | Description                                                                       |
    | ------------- | --------- | ------- | --------------------------------------------------------------------------------- |
    | `hasInternet` | output    | boolean | `true` if the last health ping succeeded. Also written to `m.global.hasInternet`. |
    | `checkNow`    | input     | boolean | Set to `true` to trigger an immediate check outside the 10-second cycle.          |

    ### Completion signal

    Long-running task. Stopped via `control = "stop"`. Output is written continuously to `hasInternet` and `m.global`.

    ### Check logic

    ```brightscript theme={null}
    sub DoCheck()
        server = GTV_ServerNormalizeBaseUrl(m.global.activeServer)
        if server = ""
            server = GTV_ServerPrimaryConnectivityUrl()
        end if
        result = GTV_PingServer(server)   ' GET to server + PATH_HEALTH, 2500ms timeout
        m.top.hasInternet    = result
        m.global.hasInternet = result
        m.global.isOnline    = GTV_IsOnline()
    end sub
    ```
  </Accordion>

  <Accordion title="HandshakeTask">
    **File:** `components/tasks/HandshakeTask.*`\
    **Function:** `RunHandshake`

    ### Purpose

    Performs the initial device handshake with the backend. Probes the server list to find a reachable endpoint, then POSTs device information to `PATH_HANDSHAKE = /api/v1/app/devices/handshake`. Sets `m.global.activeServer`, `m.global.deviceId`, `m.global.realtimeMode`, and device metadata.

    ### XML interface

    ```xml theme={null}
    <component name="HandshakeTask" extends="Task">
      <interface>
        <!-- output -->
        <field id="result"          type="assocarray" />
        <field id="done"            type="boolean"    value="false" />
        <!-- optional input -->
        <field id="forcedServerUrl" type="string"     value="" />
      </interface>
    </component>
    ```

    ### Input fields

    | Field             | Type   | Description                                                                                                           |
    | ----------------- | ------ | --------------------------------------------------------------------------------------------------------------------- |
    | `forcedServerUrl` | string | If set, bypasses auto-probe and connects only to this URL (used when the user manually selects a server in Settings). |

    ### Output fields

    | Field    | Type       | Description                             |
    | -------- | ---------- | --------------------------------------- |
    | `result` | assocarray | Handshake result (see structure below). |
    | `done`   | boolean    | Set to `true` on completion.            |

    ### Result structure

    ```brightscript theme={null}
    result = {
        success      : false,
        server       : "",         ' resolved server base URL
        realtimeMode : "polling",  ' from backend response
        errorCode    : -1,
        errorMsg     : ""
    }
    ```

    ### Completion signal

    Observe `done`. `result.success = true` means the handshake succeeded. HTTP 401/403 responses set `result.errorCode` to that HTTP code but are treated as "auth required" (not a hard failure) by `MainScene`.

    ### POST payload

    ```brightscript theme={null}
    payload = {
        platform     : "roku",
        device_id    : deviceId,
        device_model : GTV_GetBrandedDeviceModel(di),
        os_version   : osVersion,
        app_version  : GTV_GetAppVersion()   ' e.g. "1.0.7"
    }
    ```
  </Accordion>

  <Accordion title="MetricsTask">
    **File:** `components/tasks/MetricsTask.*`\
    **Function:** `RunMetrics`

    ### Purpose

    Long-running event sink. Waits for `trackEvent` field writes and POSTs each event to `PATH_METRICS = /api/v1/app/metrics/track`. Handles ad impression batch events separately via `ADS_PATH_IMPRESSIONS`.

    ### XML interface

    ```xml theme={null}
    <component name="MetricsTask" extends="Task">
      <interface>
        <!-- input -->
        <field id="trackEvent" type="assocarray" />
        <!-- output -->
        <field id="lastResult" type="assocarray" />
      </interface>
    </component>
    ```

    ### Fields

    | Field        | Direction | Type       | Description                                                    |
    | ------------ | --------- | ---------- | -------------------------------------------------------------- |
    | `trackEvent` | input     | assocarray | Event object to post. Writing this field triggers `PostEvent`. |
    | `lastResult` | output    | assocarray | Result of the most recent post: `{ok, code, server}`.          |

    ### Event object structure

    ```brightscript theme={null}
    event = {
        eventType  : "ad_shown",    ' or event_type
        adId       : "<id>",
        adFormat   : "A",
        extraData  : {}              ' optional extra key-value pairs
    }
    ```

    For impression batch events, use `eventType = "ad_impression_closed"` and add:

    ```brightscript theme={null}
    {
        eventType  : "ad_impression_closed",
        adId       : "<id>",
        stream_id  : "<id>",
        visible_ms : 4200,
        event_uuid : "<uuid>"
    }
    ```

    Impression events with `visible_ms < 1000` are silently dropped. Duplicate `event_uuid` values within a session are deduplicated.

    ### Completion signal

    Long-running task. Stopped via `control = "stop"`. Auth failures (HTTP 401/403) permanently disable metric posting for the remainder of the session.
  </Accordion>

  <Accordion title="ServerProbeTask">
    **File:** `components/tasks/ServerProbeTask.*`\
    **Function:** `RunServerProbe`

    ### Purpose

    One-shot task that probes a single server URL by calling `GTV_PingServer()`. Used by `SettingsScreen` to test connectivity to a specific server profile before applying it.

    ### XML interface

    ```xml theme={null}
    <component name="ServerProbeTask" extends="Task">
      <interface>
        <field id="baseUrl" type="string"    value="" />
        <field id="result"  type="assocarray" />
        <field id="done"    type="boolean"   value="false" />
      </interface>
    </component>
    ```

    ### Input fields

    | Field     | Type   | Description                                                   |
    | --------- | ------ | ------------------------------------------------------------- |
    | `baseUrl` | string | Server base URL to probe (e.g. `https://admin.globaltv.lat`). |

    ### Output fields

    | Field    | Type       | Description                             |
    | -------- | ---------- | --------------------------------------- |
    | `result` | assocarray | Probe result (see structure below).     |
    | `done`   | boolean    | Set to `true` when the probe completes. |

    ### Result structure

    ```brightscript theme={null}
    result = {
        success : false,
        baseUrl : "<normalized url>",
        message : "Servidor no disponible"
    }
    ```

    `success = true` means `GTV_PingServer()` received an HTTP response in the range 200–499 within `TIMEOUT_HEALTH = 2 500 ms`.

    ### Completion signal

    Observe `done`. Check `result.success` for the probe outcome.
  </Accordion>
</AccordionGroup>
