stremiok 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,82 @@
1
+ # Changelog
2
+
3
+ What changed in each version of StremioK, newest first. How everything works is in the
4
+ [user guide](docs/USER-GUIDE.md).
5
+
6
+ ## 2.2.0 — 2026-10-23
7
+ - **A TV guide for your IPTV channels:** what's on now and next on each channel's card, in
8
+ Home's details strip and while a channel plays, and its schedule when you hold OK on it. From
9
+ the guide a playlist names, an Xtream account's own, or a *Guide link* you set (Settings ›
10
+ Content › IPTV).
11
+ - **A Guide tab:** your channels as a grid across 2½ hours that follows the clock; OK on what's
12
+ on now plays it, and CH+ / CH− then go through the Guide's channels. The remote's GUIDE key
13
+ opens it.
14
+ - **Guide-only channels** (`tvg-type="guide"`): a channel you watch elsewhere (a cable box, say)
15
+ on the Guide tab with its schedule; OK on a film finds it in your addons.
16
+ - **Xtream accounts:** add one with its server, username and password (checked with the server
17
+ first). Its channels play as MPEG-TS; *Format* switches the account to HLS, or one channel
18
+ while it plays (the player's menu), kept for that channel.
19
+ - **★ Favorite channels:** hold OK on a channel card, *Add to favorites*: a row of their own,
20
+ first on the IPTV and Guide tabs.
21
+ - **Closed captions on live channels:** captions inside an HLS channel's picture (CC1–CC4, the
22
+ kind VLC shows) are offered in the subtitle menu, and turn into subtitles when you pick them.
23
+ On an Xtream channel, switch *Format* to HLS for them.
24
+ - **In the player:** picture mode kept per channel; *Reload* and a *Sleep timer* in the menu;
25
+ the time of day at the top right.
26
+ - **More channels play:** more ways a playlist gives a channel's User-Agent and Referer, Xtream
27
+ channels that lead to YouTube, and channel logos from the TV guide.
28
+ - **Put the top menu in your own order:** Settings › Appearance › *Menu*.
29
+ - **The IPTV tab has live TV only:** a catalog of films or series sits on Movies or Series.
30
+ - **Fixes:** USB downloads checked part by part; a broken subtitle file or TV guide can't hold
31
+ the remote; a first playlist shows the IPTV tab at once.
32
+
33
+ ## 2.1.0 — 2026-10-20
34
+ - **Live channels reconnect** when their connection drops, as often as *Reconnect live channels*
35
+ says (Settings › Playback › Seeking & buffering): Off, A few times or Keep trying.
36
+ - **Trakt:** your whole watched history is read again (Trakt now sends it a page at a time, and
37
+ shows without their episodes unless asked), and an episode you left part-way no longer hides
38
+ the show's next one once you've watched past it, on any device.
39
+ - **Addons:** turn one off or on, as you can a playlist, each shown with its logo (Settings ›
40
+ Content › Addons); "No sources found" says why, addon by addon; an addon's error page is no
41
+ longer shown as a title.
42
+ - **A YouTube tab on Home:** StremioK's YouTube began as a way to watch TV channels that stream
43
+ on YouTube rather than through an M3U playlist, so its catalogs sat in the IPTV tab. Now that
44
+ it also covers channels, playlists, searches and other addons' YouTube catalogs, it has a tab
45
+ of its own, between Sports and Local.
46
+ - **Reordering:** ⇈ / ⇊, beside ▲ / ▼, move an item straight to the top or bottom of any list you
47
+ put in order: addons, playlists, YouTube catalogs, Home feeds, Search results and source
48
+ ranking.
49
+ - **Playback:** a source counts as started only once it plays, so one that fails right after
50
+ opening gives way to the next; video quality for streams that need their own headers; skip
51
+ times from TheIntroDB's new API.
52
+ - **A faster start:** your addons don't wait for the IPTV playlists, and Home's rows appear as
53
+ each is ready.
54
+ - **Security and privacy fixes:** USB drives and downloads; the TV itself off-limits to
55
+ downloads and live relays; AIOManager's key kept to its own server, and addons you added
56
+ yourself left alone by its sync.
57
+
58
+ ## 2.0.0 — 2026-10-17
59
+ The first public version of StremioK, a Stremio-addon client for Samsung Tizen TVs (Tizen 3.0
60
+ and later). What it does is in the [README](README.md#highlights); installing it, in the
61
+ [installation guide](docs/INSTALL.md).
62
+
63
+ - **Your addons:** catalogs, title details, sources and subtitles from the Stremio addons you add
64
+ by link, or import from your Stremio account, AIOManager or a configuration file.
65
+ - **Finding something to watch:** Home with Watching, Calendar, Movies, Series, IPTV, Radio,
66
+ Sports and Local tabs; Discover; Search across every addon that can search, and people by
67
+ name.
68
+ - **Title pages:** ranked sources with ▶ Play best, filters by addon and resolution, trailers,
69
+ ratings, Cast & more, collections, episode pages and an episode ratings grid.
70
+ - **Playing:** resume, skip intros, recaps and credits, the next episode on its own, the next
71
+ source when one doesn't start, subtitles from addons, the file, YouTube and your USB drive,
72
+ with timing and sync by ear, audio tracks remembered, private viewing.
73
+ - **Trakt:** watched marks and viewing progress sync with Trakt; Up next and watchlist rows
74
+ are read from your account.
75
+ - **IPTV and radio** from your M3U playlists; **YouTube** channels, playlists, searches and live
76
+ streams, with SponsorBlock, DeArrow, chapters and captions; **a USB drive's** videos matched
77
+ to their titles, and downloads to it.
78
+ - **Documentation** for every reader: the [user guide](docs/USER-GUIDE.md), a
79
+ [settings reference](docs/SETTINGS.md), [troubleshooting](docs/TROUBLESHOOTING.md),
80
+ [privacy and data](docs/PRIVACY.md), a [technical reference](docs/TECHNICAL-REFERENCE.md)
81
+ and the [developer guide](docs/DEVELOPER-GUIDE.md). Settings' on-screen hints say the effect
82
+ in a sentence or two.
package/README.md CHANGED
@@ -22,7 +22,7 @@ video through Samsung's AVPlay API.
22
22
  - **Your addons:** catalogs, details, sources and subtitles from the Stremio addons you add
23
23
  (Cinemeta and OpenSubtitles to start with), in the order you choose.
24
24
  - **Home** with Watching (Continue Watching, synced with **Trakt** if you connect it), Movies,
25
- Series, IPTV, Radio, Sports and Local tabs; **Discover** to browse any catalog in full;
25
+ Series, IPTV, Guide, Radio, Sports, YouTube and Local tabs; **Discover** to browse any catalog in full;
26
26
  **Search** across every addon that can search, and people by name.
27
27
  - **Title pages:** ranked sources with **▶ Play best**, trailers, ratings (IMDb, Rotten
28
28
  Tomatoes, Metacritic… with your own API keys), seasons and episodes with their own pages,
@@ -30,7 +30,7 @@ video through Samsung's AVPlay API.
30
30
  - **The player:** resume where you stopped, skip intros, recaps and credits, next episode,
31
31
  subtitles (addons', the file's own, and your own files on a USB drive) with your choice of look, audio
32
32
  tracks remembered, switching source mid-playback, private viewing.
33
- - **IPTV and radio** from your M3U playlists, with CH+ / CH−.
33
+ - **IPTV and radio** from your M3U playlists and Xtream accounts, with CH+ / CH−.
34
34
  - **YouTube:** live channels, trailers, and your own YouTube channels, playlists and searches,
35
35
  with SponsorBlock, DeArrow, chapters and captions.
36
36
  - **Local:** the videos on a USB drive, matched to their titles.
@@ -55,7 +55,8 @@ Three ways, all covered step by step in the **[installation guide](docs/INSTALL.
55
55
  2. **API keys**, optional: your own TMDB, OMDb and MDBList keys add ratings, *Cast & more*,
56
56
  *where to watch* and episode trailers.
57
57
  3. **Trakt**, optional: connect your account to share watched marks and Continue Watching.
58
- 4. **IPTV**, optional: your M3U playlist links.
58
+ 4. **IPTV**, optional: your M3U playlist links, or an Xtream account (its server, username and
59
+ password).
59
60
  5. **A USB drive**, optional: plug it in and the Local tab appears.
60
61
 
61
62
  [Getting started](docs/USER-GUIDE.md#getting-started) in the user guide walks through each.
@@ -73,15 +74,16 @@ a direct HTTP(S)/HLS/DASH URL, and this app embeds no torrent or debrid engine.
73
74
  > shows that notice on it); its results are rent/buy/subscription *links to external
74
75
  > services*, not video URLs. Add stream addons that return HTTP/HLS URLs for actual playback.
75
76
 
76
- The **IPTV** feature likewise hosts no content: you supply your own M3U playlist URLs. For
77
- YouTube entries, the bundled service finds the video's stream; the video itself comes
78
- straight from YouTube. Nothing is saved anywhere unless you download a source to your own USB
79
- drive.
77
+ The **IPTV** feature likewise hosts no content: you supply your own M3U playlist URLs or Xtream
78
+ accounts. For YouTube entries, the bundled service finds the video's stream; the video itself
79
+ comes straight from YouTube. No video or audio is kept: everything plays straight from its
80
+ source, unless you download it to your own USB drive. What StremioK does keep, on the TV, is
81
+ your progress, settings and copies that let it start faster (see
82
+ [Privacy and data](docs/PRIVACY.md#what-stays-on-the-tv)).
80
83
 
81
84
  ## Known limitations
82
85
 
83
86
  - AVPlay behavior varies by TV generation, codec and HLS provider.
84
- - No TV guide (now / next) for IPTV channels yet.
85
87
  - No torrent playback: sources need a direct HTTP(S)/HLS/DASH URL (e.g. through a debrid
86
88
  addon).
87
89
  - Home rows show a catalog's first page (up to 30 titles); Discover pages through the rest.
package/config.xml CHANGED
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <widget xmlns="http://www.w3.org/ns/widgets" xmlns:tizen="http://tizen.org/ns/widgets"
3
- id="http://stremiok.tizen/StremioKTizen" version="2.0.0" viewmodes="maximized">
3
+ id="http://stremiok.tizen/StremioKTizen" version="2.2.0" viewmodes="maximized">
4
4
  <tizen:application id="StremioK01.StremioK" package="StremioK01" required_version="3.0"/>
5
5
  <content src="index.html"/>
6
6
  <feature name="http://tizen.org/feature/screen.size.all"/>
package/css/style.css CHANGED
@@ -40,7 +40,7 @@ html, body {
40
40
  .header .spacer { flex: 1; }
41
41
  .header-title { font-size: 36px; font-weight: bold; }
42
42
  /* Search and Settings: pills like the tabs (the top menu fits every tab, Local included, at 1920 px) */
43
- .header .hchip { display: inline-flex; align-items: center; font-size: 22px; padding: 8px 13px; margin-right: 6px; border-radius: 28px; background: #1a1f2b; color: #aeb6c4; border: 4px solid transparent; white-space: nowrap; }
43
+ .header .hchip { display: inline-flex; align-items: center; font-size: 22px; padding: 8px 9px; margin-right: 5px; border-radius: 28px; background: #1a1f2b; color: #aeb6c4; border: 4px solid transparent; white-space: nowrap; }
44
44
  .header .hchip.focusable.focused { background: #818cf8; color: #0e1117; border-color: #fff; opacity: 1; }
45
45
 
46
46
  /* --- Rows --- */
@@ -335,6 +335,13 @@ html, body {
335
335
  .playlist-row .playlist-item { flex: 1; margin-bottom: 0; }
336
336
  .feed-off .playlist-item { opacity: 0.5; }
337
337
  .playlist-item .addon-said:empty { display: none; }
338
+ /* an addon's logo (Settings › Content › Addons): its initial until the logo has loaded, then the logo over it */
339
+ .addon-item.has-logo { position: relative; padding-left: 92px; min-height: 56px; }
340
+ .addon-logo { position: absolute; left: 18px; top: 16px; width: 56px; height: 56px; border-radius: 8px; overflow: hidden; background: #0d1119; color: #8b93a5; font-size: 26px; font-weight: bold; line-height: 56px; text-align: center; word-break: normal; }
341
+ .addon-logo img { position: absolute; left: 0; top: 0; width: 100%; height: 100%; object-fit: contain; visibility: hidden; }
342
+ .addon-logo:before { content: attr(data-initial); }
343
+ .addon-logo.loaded:before { visibility: hidden; }
344
+ .addon-logo.loaded img { visibility: visible; }
338
345
  /* list picker (single choice) */
339
346
  .pick-box { width: 720px; text-align: left; }
340
347
  .pick-box .modal-msg { text-align: left; margin-bottom: 20px; }
@@ -367,12 +374,13 @@ html, body {
367
374
 
368
375
  /* Home top row: tab pills + Search + Settings on the left, StremioK logo on the right */
369
376
  .home-header .tabbar-inline { display: flex; align-items: center; }
370
- .tab-pill { display: inline-flex; align-items: center; font-size: 22px; padding: 8px 13px; margin-right: 6px; border-radius: 28px; background: #1a1f2b; color: #aeb6c4; border: 4px solid transparent; white-space: nowrap; }
377
+ .tab-pill { display: inline-flex; align-items: center; font-size: 22px; padding: 8px 9px; margin-right: 5px; border-radius: 28px; background: #1a1f2b; color: #aeb6c4; border: 4px solid transparent; white-space: nowrap; }
371
378
  .tab-ic { margin-left: 6px; font-size: 22px; }
372
379
  .tab-pill.active { background: #2a3346; color: #fff; }
373
380
  .tab-pill.focusable.focused { background: #818cf8; color: #0e1117; border-color: #fff; }
374
381
  .home-header .hchip.active { background: #2a3346; color: #fff; opacity: 1; }
375
382
  .row-empty { margin: 50px 60px; font-size: 26px; color: #8b93a3; }
383
+ .row-loading .row-empty { margin: 10px 60px 26px 60px; font-size: 22px; }
376
384
 
377
385
  /* --- Subtitle fonts (bundled; SIL Open Font License, see css/fonts/OFL-*.txt) ---
378
386
  Latin, Latin-extended and Cyrillic subsets; unicode-range makes the webview
@@ -505,3 +513,30 @@ html, body {
505
513
  .src-s { background: #7b5bf5; }
506
514
  .src-a { background: #0f9f8f; }
507
515
  .src-c { background: #6b7280; }
516
+ /* an IPTV channel card: what's on now (its TV guide), and how far along it is, along its top (its name is at the bottom) */
517
+ .card-epg { position: absolute; left: 0; right: 0; top: 0; padding: 5px 8px 4px; background: rgba(10, 12, 18, 0.82); }
518
+ .card-epg-t { font-size: 15px; color: #e6e9ef; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
519
+ .card-epg-bar { height: 3px; margin-top: 3px; background: rgba(255, 255, 255, 0.18); border-radius: 2px; overflow: hidden; }
520
+ .card-epg-fill { display: block; height: 100%; background: #818cf8; }
521
+ /* the Guide tab (js/view-guide.js): the hours along the top, then a row a channel; programs
522
+ placed by their time (left / width in %), what's on now lighter, a line at the time now */
523
+ .guide { padding: 0 60px 20px 60px; } /* (the same sides as Home's rows: .row-title, .row-scroller) */
524
+ .guide-head { display: flex; height: 34px; }
525
+ .guide-corner { width: 230px; flex: none; font-size: 17px; color: #8b93a5; line-height: 34px; }
526
+ .guide-hours { position: relative; flex: 1; }
527
+ .guide-time { position: absolute; top: 0; font-size: 17px; color: #aeb6c4; line-height: 34px; padding-left: 6px; border-left: 2px solid #2a3346; }
528
+ .guide-row { display: flex; height: 62px; margin-bottom: 6px; }
529
+ .guide-ch { width: 230px; flex: none; padding: 0 12px; font-size: 19px; font-weight: bold; line-height: 62px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: #121722; border-radius: 8px 0 0 8px; }
530
+ .guide-track { position: relative; flex: 1; overflow: hidden; background: #0e1219; }
531
+ .guide-prog { position: absolute; top: 0; bottom: 0; padding: 0 10px; font-size: 18px; line-height: 56px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: #161b26; border: 3px solid #0e1117; border-radius: 6px; color: #c7cbd4; }
532
+ .guide-prog.now { background: #222b3d; color: #fff; }
533
+ .guide-prog.guide-wait { color: #8b93a5; font-style: italic; } /* (a row whose programs are still being read) */
534
+ .guide-prog.focusable.focused { background: #818cf8; color: #0e1117; border-color: #fff; }
535
+ .guide-nowline { position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px; background: #f87171; }
536
+ .guide-ch-logo { height: 34px; max-width: 56px; margin-right: 10px; vertical-align: middle; object-fit: contain; }
537
+ /* a program's picture (the TV guide), at the strip's right; its text kept clear of it */
538
+ .home-info-pic { position: absolute; right: 16px; top: 50%; height: 118px; width: 210px; margin-top: -59px; object-fit: cover; border-radius: 8px; }
539
+ .home-info.has-pic { padding-right: 240px; }
540
+ .home-info-pic.hidden { display: none; }
541
+ #osd-clock { position: fixed; top: 40px; right: 60px; font-size: 34px; font-weight: bold; color: #fff; text-shadow: 0 2px 8px rgba(0,0,0,0.9); } /* (the time of day, top right, while the bar shows) */
542
+ #osd-clock:empty { display: none; }
package/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=1920, height=1080, user-scalable=no">
6
6
  <title>StremioK</title>
7
- <link rel="stylesheet" href="css/style.css?v=2.0.0">
7
+ <link rel="stylesheet" href="css/style.css?v=2.2.0">
8
8
  </head>
9
9
  <body>
10
10
  <!-- CSS-applied sentinel (kept off-screen); app.js waits for its width before rendering -->
@@ -32,6 +32,7 @@
32
32
  <div id="osd-chapter" class="hidden"></div>
33
33
  <div id="osd-bar"><div id="osd-progress"></div><div id="osd-sponsor"></div><div id="osd-chapters"></div><div id="osd-buffer"></div><div id="osd-scrub-range" class="hidden"></div><div id="osd-scrub" class="hidden"><div id="osd-scrub-time"></div></div></div>
34
34
  <div id="osd-times"><span id="osd-cur">00:00</span><span class="osd-right"><span id="osd-tags"></span><span id="osd-dur">00:00</span></span></div>
35
+ <div id="osd-clock"></div>
35
36
  <div id="osd-hint">OK: Play/Pause &nbsp; &larr;/&rarr;: Seek &nbsp; RETURN: Back</div>
36
37
  </div>
37
38
 
@@ -52,35 +53,37 @@
52
53
 
53
54
  <!-- Load order matters: polyfills -> util -> iptv -> data -> input -> tvapps -> player parts -> player -> ytplay -> view parts -> views -> app -->
54
55
  <!-- ?v= busts the TV webview cache on every release (keep in sync with package.json) -->
55
- <script src="js/polyfills.js?v=2.0.0"></script>
56
- <script src="js/util.js?v=2.0.0"></script>
57
- <script src="js/iptv.js?v=2.0.0"></script>
58
- <script src="js/youtube.js?v=2.0.0"></script>
59
- <script src="js/stremio.js?v=2.0.0"></script>
60
- <script src="js/trakt.js?v=2.0.0"></script>
61
- <script src="js/keys.js?v=2.0.0"></script>
62
- <script src="js/focus.js?v=2.0.0"></script>
63
- <script src="js/tvapps.js?v=2.0.0"></script>
64
- <script src="js/player-core.js?v=2.0.0"></script>
65
- <script src="js/player-store.js?v=2.0.0"></script>
66
- <script src="js/player-lang.js?v=2.0.0"></script>
67
- <script src="js/player-quality.js?v=2.0.0"></script>
68
- <script src="js/player-subs.js?v=2.0.0"></script>
69
- <script src="js/player-audio.js?v=2.0.0"></script>
70
- <script src="js/player-controls.js?v=2.0.0"></script>
71
- <script src="js/player.js?v=2.0.0"></script>
72
- <script src="js/ytplay.js?v=2.0.0"></script>
73
- <script src="js/local.js?v=2.0.0"></script>
74
- <script src="js/downloads.js?v=2.0.0"></script>
75
- <script src="js/stremio-account.js?v=2.0.0"></script>
76
- <script src="js/view-core.js?v=2.0.0"></script>
77
- <script src="js/view-home.js?v=2.0.0"></script>
78
- <script src="js/view-detail.js?v=2.0.0"></script>
79
- <script src="js/view-search.js?v=2.0.0"></script>
80
- <script src="js/view-settings.js?v=2.0.0"></script>
81
- <script src="js/view-discover.js?v=2.0.0"></script>
82
- <script src="js/views.js?v=2.0.0"></script>
83
- <script src="js/app.js?v=2.0.0"></script>
84
- <script src="js/hubpreview.js?v=2.0.0"></script>
56
+ <script src="js/polyfills.js?v=2.2.0"></script>
57
+ <script src="js/util.js?v=2.2.0"></script>
58
+ <script src="js/iptv.js?v=2.2.0"></script>
59
+ <script src="js/epg.js?v=2.2.0"></script>
60
+ <script src="js/youtube.js?v=2.2.0"></script>
61
+ <script src="js/stremio.js?v=2.2.0"></script>
62
+ <script src="js/trakt.js?v=2.2.0"></script>
63
+ <script src="js/keys.js?v=2.2.0"></script>
64
+ <script src="js/focus.js?v=2.2.0"></script>
65
+ <script src="js/tvapps.js?v=2.2.0"></script>
66
+ <script src="js/player-core.js?v=2.2.0"></script>
67
+ <script src="js/player-store.js?v=2.2.0"></script>
68
+ <script src="js/player-lang.js?v=2.2.0"></script>
69
+ <script src="js/player-quality.js?v=2.2.0"></script>
70
+ <script src="js/player-subs.js?v=2.2.0"></script>
71
+ <script src="js/player-audio.js?v=2.2.0"></script>
72
+ <script src="js/player-controls.js?v=2.2.0"></script>
73
+ <script src="js/player.js?v=2.2.0"></script>
74
+ <script src="js/ytplay.js?v=2.2.0"></script>
75
+ <script src="js/local.js?v=2.2.0"></script>
76
+ <script src="js/downloads.js?v=2.2.0"></script>
77
+ <script src="js/stremio-account.js?v=2.2.0"></script>
78
+ <script src="js/view-core.js?v=2.2.0"></script>
79
+ <script src="js/view-home.js?v=2.2.0"></script>
80
+ <script src="js/view-guide.js?v=2.2.0"></script>
81
+ <script src="js/view-detail.js?v=2.2.0"></script>
82
+ <script src="js/view-search.js?v=2.2.0"></script>
83
+ <script src="js/view-settings.js?v=2.2.0"></script>
84
+ <script src="js/view-discover.js?v=2.2.0"></script>
85
+ <script src="js/views.js?v=2.2.0"></script>
86
+ <script src="js/app.js?v=2.2.0"></script>
87
+ <script src="js/hubpreview.js?v=2.2.0"></script>
85
88
  </body>
86
89
  </html>
package/js/app.js CHANGED
@@ -181,6 +181,7 @@ var App = (function () {
181
181
  if (nowb - _lastBackAt < 500) { return true; }
182
182
  _lastBackAt = nowb;
183
183
  }
184
+ if (action === 'GUIDE') { guideKey(); return true; }
184
185
  if (Player.isActive()) { return Player.handleKey(action); } // (a held OK too: the player's Cast & more has a menu behind it)
185
186
  if (Views.moveKey && Views.moveKey(action)) { return true; } // (a Continue Watching card being moved)
186
187
  switch (action) {
@@ -213,16 +214,27 @@ var App = (function () {
213
214
  }
214
215
  }
215
216
 
216
- // Number keys away from the player: 0 Settings, 1–8 Home's tabs as they show (1: Watching).
217
- // Not over a popup, nor on a page where something's being typed.
217
+ // The remote's GUIDE key: the Guide tab (once a TV guide is read: it's in the top menu), the
218
+ // player closed first. Not over a popup, nor while an addon's link is typed.
219
+ function guideKey() {
220
+ var on = (Views.menuItems ? Views.menuItems() : []).some(function (m) { return m.key === 'guide'; });
221
+ if (!on) { U.toast('No TV guide yet (Settings › Content › IPTV › Guide link)', 3000); return; }
222
+ if (Player.isActive()) { Player.exit(); }
223
+ else if (Views.isModalOpen() || _current === 'addaddon') { return; }
224
+ goHome('guide');
225
+ }
226
+
227
+ // Number keys away from the player: 0 Settings, 1–9 the top menu's items as they show (1:
228
+ // Watching, the rest in your order: Views.menuItems). Not over a popup, nor on a page where
229
+ // something's being typed.
218
230
  function numberKey(n) {
219
231
  if (Views.isModalOpen() || _current === 'addaddon') { return; }
220
232
  if (n === 0) { if (_current !== 'settings') { openSettings(); } return; }
221
- // the top menu's items, left to right: the tabs showing, then Discover, then Search
222
- var tabs = Views.availableTabs ? Views.availableTabs() : [], t = tabs[n - 1];
223
- if (t) { goHome(t.key); }
224
- else if (n === tabs.length + 1) { if (_current !== 'discover') { openDiscover(); } }
225
- else if (n === tabs.length + 2) { if (_current !== 'search') { openSearch(); } }
233
+ var m = (Views.menuItems ? Views.menuItems() : [])[n - 1];
234
+ if (!m) { return; }
235
+ if (m.key === 'discover') { if (_current !== 'discover') { openDiscover(); } }
236
+ else if (m.key === 'search') { if (_current !== 'search') { openSearch(); } }
237
+ else { goHome(m.key); }
226
238
  }
227
239
 
228
240
  function actualStart() {
package/js/downloads.js CHANGED
@@ -13,7 +13,8 @@ var Downloads = (function () {
13
13
  return typeof YTPlay !== 'undefined' && YTPlay.installMode() === 'wgt' && typeof LocalMedia !== 'undefined' && LocalMedia.present && LocalMedia.present();
14
14
  }
15
15
  function kept() { var l = U.store.get(DL_KEY, []); return Array.isArray(l) ? l : []; }
16
- function keep(l) { U.store.set(DL_KEY, l.slice(-MAX)); }
16
+ var _keptJson = null; // (what was last written: the list polled every few seconds is written only when it changed)
17
+ function keep(l) { var v = l.slice(-MAX), j = JSON.stringify(v); if (j === _keptJson) { return; } _keptJson = j; U.store.set(DL_KEY, v); }
17
18
  function keyOf(x) { return x.dir + '/' + x.name; }
18
19
  // The same file, renamed or not ("Film.mkv", "Film (k3x9).mkv"): its folder and its name's stem.
19
20
  function sameFile(y, k) {
@@ -55,11 +56,13 @@ var Downloads = (function () {
55
56
  });
56
57
  _synced = true;
57
58
  emit();
58
- return U.settleAll(again.map(function (k) { return function () { return send(k); }; }), 1).then(function () { return _items; });
59
+ return U.settleAll(again.map(function (k) { return function () { return send(k, true); }; }), 1).then(function () { return _items; });
59
60
  }, function () { return _items; });
60
61
  }
61
- function send(k) {
62
- return ask('/fsdl/add?' + qs({ dir: k.dir, name: k.name, url: k.url, ua: k.ua, ref: k.ref, size: k.size, title: k.title }), 20000).then(function (x) {
62
+ // again: the service no longer knows it (it restarted): it checks the drive first, so one it had
63
+ // finished isn't downloaded a second time.
64
+ function send(k, again) {
65
+ return ask('/fsdl/add?' + qs({ dir: k.dir, name: k.name, url: k.url, ua: k.ua, ref: k.ref, size: k.size, title: k.title, again: again ? '1' : '' }), 20000).then(function (x) {
63
66
  var mine = kept();
64
67
  mine.forEach(function (y) { if (keyOf(y) === keyOf(k)) { y.id = x.id; y.state = x.state; y.err = ''; } });
65
68
  keep(mine);
@@ -129,6 +132,14 @@ var Downloads = (function () {
129
132
 
130
133
  // ---- starting one, from a title's source (view-detail's source menu) ----
131
134
  var EXT = /\.(mkv|mp4|m4v|avi|ts|webm|mov|wmv|mpe?g)$/i;
135
+ // Only a file can be downloaded: a link (http/https) that isn't a streamed playlist (HLS
136
+ // .m3u8, DASH .mpd), which would save just the list, not the video. (One whose link doesn't
137
+ // say is checked by StremioK's service, by what the server answers.)
138
+ function isFileLink(stream) {
139
+ var u = '' + ((stream && stream.url) || ''), path = u.split(/[?#]/)[0], query = u.split('#')[0].split('?')[1] || '';
140
+ if (!/^https?:\/\//i.test(u)) { return false; }
141
+ return !/\.(m3u8?|mpd)$/i.test(path) && !/(^|&)(type|format|ext|output)=(m3u8|hls|mpd|dash)(&|$)/i.test(query);
142
+ }
132
143
  function fileNameFor(stream, label) {
133
144
  var bh = stream.behaviorHints || {}, path = ('' + (stream.url || '')).split(/[?#]/)[0];
134
145
  var named = typeof bh.filename === 'string' && EXT.test(bh.filename) ? bh.filename : '';
@@ -150,6 +161,7 @@ var Downloads = (function () {
150
161
  // ui: { pick(title, options, onPick), confirm(msg, ok, fn) } (the views' popups)
151
162
  function start(stream, t, ui) {
152
163
  if (!available()) { U.toast('Plug a USB drive into the TV first'); return; }
164
+ if (!isFileLink(stream)) { U.toast('This source is a streamed playlist (HLS or DASH), not a file: it can’t be downloaded. Pick another source.', 6000); return; }
153
165
  var name = fileNameFor(stream, t.label), bytes = +((stream.behaviorHints || {}).videoSize) || 0;
154
166
  spaces().then(function (list) {
155
167
  if (!list.length) { U.toast('No USB drive'); return; }
@@ -202,6 +214,6 @@ var Downloads = (function () {
202
214
  }, function () {});
203
215
  }
204
216
 
205
- return { available: available, items: items, sync: sync, watch: watch, onChange: onChange, add: add, cancel: cancel, clear: clear, retry: retry,
217
+ return { available: available, isFileLink: isFileLink, items: items, sync: sync, watch: watch, onChange: onChange, add: add, cancel: cancel, clear: clear, retry: retry,
206
218
  start: start, fileNameFor: fileNameFor, busy: busy, synced: function () { return _synced; } };
207
219
  })();