Playback pipeline¶
Once you pick a source, NeNeTeePee-Stream-Kodi submits it, polls until it's ready, discovers the playable file, and hands Kodi a local URL. This page covers the nzbdav backend; the NZBGet backend follows a parallel path.
The no-hang contract¶
The resolve path has one non-negotiable rule: Kodi must always receive a
resolution. When Kodi asks the add-on to resolve a plugin:// URL, it blocks
until the add-on calls setResolvedUrl: True with a playable URL, or False
on any failure, cancellation, or timeout. Every branch routes through a
resolution call, even exceptions and a corrupt-settings read. The resolver
deliberately places settings reads and dialog creation inside the try/except so
a rare failure still resolves the handle instead of hanging Kodi.
TMDBHelper's default player entry uses RunScript, which reaches
resolve_and_play() with no plugin handle. That path starts playback with
xbmc.Player().play(...) and on failure only notifies you. The same try/except
structure makes sure every exception still ends in a clean notification.
Submit → poll → resolve¶
sequenceDiagram
participant R as Resolver
participant N as nzbdav API
participant W as WebDAV
participant X as Stream proxy
R->>N: submit_nzb (mode=addurl)
Note over R,N: UI pump every 250ms,<br/>probe threads adopt the job<br/>if it appears before addurl replies
N-->>R: nzo_id (or timeout, then probe and adopt)
loop poll (interval 1s, timeout 3600s)
par
R->>N: queue status (nzo_id)
and
R->>N: history status (nzo_id)
end
N-->>R: status / percent / storage
end
R->>W: discover video file in completed folder
W-->>R: video href (episode-matched, stub-checked)
R->>X: POST /prepare
X-->>R: local stream URL
R->>R: resume prompt → setResolvedUrl(True) / Player.play
Submission¶
Submission runs on a worker thread while the plugin thread pumps the progress dialog every 250 ms and watches for cancellation. Concurrently, probe threads watch the queue and history for the job by name and adopt it the moment it appears, often before the submit call even returns. Submission retries up to three attempts, two seconds apart (the wait is abortable), and classifies errors carefully:
- A client-side submit timeout is not a failure: nzbdav may still be fetching and parsing the NZB (routinely slow on a large remux). NeNeTeePee-Stream-Kodi probes the queue and history to adopt a slow-but-successful submit rather than double-submitting.
- Transient HTTP errors (408/502/503/504) retry. An explicit nzbdav rejection or a 4xx is terminal and surfaces immediately.
- Other HTTP errors (for example a 500 "duplicate nzo_id") first probe the queue and history and adopt a matching job. If none turns up, the error is terminal.
- A "too many requests" error (429) shows a rate-limit notification naming the indexer instead of the generic error dialog.
Polling¶
Each poll queries the queue and history APIs in parallel. Because nzbdav can remap the job id when a job moves from queue to history, NeNeTeePee-Stream-Kodi also has a by-name history fallback, gated by the submit timestamp so a stale prior attempt can't trigger a false failure. When neither API answers within the poll window, NeNeTeePee-Stream-Kodi probes WebDAV reachability to tell a missing job apart from an unreachable server. The progress dialog maps the backend status to a line:
| Status | Dialog line |
|---|---|
| Queued | Queued… |
| Fetching | Fetching NZB… |
| Propagating | Waiting for propagation… |
| Downloading | Downloading… n% |
| Paused | Paused |
| Failed | nzbdav's failure message, or Download failed |
Finding the right video file¶
When a job completes, NeNeTeePee-Stream-Kodi maps the completed storage path to a WebDAV path
and lists the folder with a PROPFIND (Depth: 1 per level, recursion capped
a few levels deep, XML parsed with entity declarations refused). It then chooses
the playable file:
- Video extensions recognized:
.mkv,.mp4,.avi,.m4v,.ts,.m2ts,.wmv,.mov. - For a TV season pack, NeNeTeePee-Stream-Kodi matches the requested season/episode against filenames, handling multi-episode and range patterns, and recurses into subfolders when needed. A named wrong episode fails closed rather than being selected for its size.
- With an explicit episode request, one generic video may retain the ordinary single-file fallback. Multiple untagged videos are ambiguous and fail closed instead of selecting one by size. Without episode context, discovery preserves the legacy largest-video behavior.
Guarding against "Completed but broken"¶
A backend can report Completed while the file is really a placeholder or is missing its middle article bodies. NeNeTeePee-Stream-Kodi runs two guards, and both fail open (they only reject on positive evidence of a problem, never on missing data):
- Stub guard: rejects the folder when its total video bytes are under half the advertised release size, or when the picked file is a tiny fraction of the largest sibling video. This catches nzbdav's ~30-second job-start placeholder.
- Body guard: after a
HEAD, issues a 64 KiB rangeGETfrom the middle of the file. A≥400or an empty body means the bodies aren't really there.
Remembering completed season packs¶
Beta feature
Added in 2.0.0-beta.2, available on the Beta channel.
After a confirmed completed-folder inventory, NeNeTeePee-Stream-Kodi records a folder
containing at least two reliably named episodes from exactly one season for
later reuse. For nzbdav/WebDAV, NeNeTeePee-Stream-Kodi defers recording until the selected
stream passes body validation. NZBGet records from the reachable completed-folder
inventory on its SMB or local/mounted completed-downloads path. NeNeTeePee-Stream-Kodi stores
the catalog in the add-on profile at
special://profile/addon_data/plugin.video.nzbdav/season_packs.json and bounds
it to the 100 most recently confirmed jobs.
One record represents one completed backend job. Its key is the backend plus
the exact native job identifier (nzo_id for nzbdav or NZBID for NZBGet), and
it also retains that job's native completed folder. NeNeTeePee-Stream-Kodi never combines
records by filename or release name.
For a later request, the router prepends an already-downloaded season-pack row only when the catalog says that exact episode is present. Selection validates the exact history identifier and folder, inventories the folder again, and requires an exact episode match before playback. It doesn't submit another NZB or substitute a differently named episode. Conclusively missing or changed jobs become stale. Transient API, authentication, network, parsing, or storage errors fail soft and preserve the record for a later attempt.
Queue clearing¶
Before submitting, NeNeTeePee-Stream-Kodi can clear nzbdav's download queue, controlled by Clear download queue when starting a new download (Advanced › Polling; Ask / Always clear / Never, default Ask). It excludes this title's own in-flight job, skips clearing when a completed copy you could reuse already exists, and (in Ask mode) shows a Keep/Clear prompt before the progress dialog so it's never hidden behind the modal. Any probe or dialog failure leaves the queue untouched.
Resume¶
NeNeTeePee-Stream-Kodi tracks resume points per release identity (title + size + post date),
not per stream URL, so resume survives the churning proxy/WebDAV URL and stays
distinct per episode. On replay it shows Kodi's native Resume from… /
Start from beginning prompt (honoring your Kodi play-action preference). It
also scrubs Kodi's own bookmark for the outer plugin:// URL, which otherwise
causes a replay to try reopening the plugin URL as a stream and fail.
Handing off to the proxy¶
On a validated, playable stream, the resolver POSTs to the service's loopback
/prepare endpoint, receives a local URL, and resolves the resume choice. It
then sets the nzbdav.active / nzbdav.stream_url Home-window properties for
the service's playback monitor and starts playback: setResolvedUrl(True) on
the plugin:// path, xbmc.Player().play(...) on the RunScript path. Playback
then flows entirely through the stream proxy.