stremiok 2.2.0 → 2.2.2
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 +37 -0
- package/README.md +8 -6
- package/config.xml +1 -1
- package/css/style.css +11 -8
- package/index.html +33 -33
- package/js/app.js +24 -22
- package/js/focus.js +5 -5
- package/js/iptv.js +15 -2
- package/js/player-core.js +71 -9
- package/js/player-lang.js +66 -5
- package/js/player-store.js +4 -3
- package/js/player-subs.js +67 -13
- package/js/stremio.js +154 -23
- package/js/util.js +55 -13
- package/js/view-core.js +43 -13
- package/js/view-detail.js +36 -8
- package/js/view-discover.js +1 -1
- package/js/view-guide.js +41 -2
- package/js/view-home.js +33 -8
- package/js/view-settings.js +208 -57
- package/js/views.js +2 -2
- package/js/ytplay.js +4 -3
- package/package.json +2 -1
- package/service/index.js +33 -9
package/js/view-detail.js
CHANGED
|
@@ -203,6 +203,10 @@
|
|
|
203
203
|
if (local) { resumeSources(startPlayback(local, p.id, p.name, how), p.id); return; }
|
|
204
204
|
var st = { url: p.url, name: p.name, _headers: U.sanitizeHeaders(p.headers) || undefined,
|
|
205
205
|
behaviorHints: (p.fileHints && typeof p.fileHints === 'object') ? p.fileHints : undefined }; // (its file: for subtitles made for it)
|
|
206
|
+
if (p.torrent && typeof p.torrent.infoHash === 'string') { // (a torrent: its link from the streaming server set now)
|
|
207
|
+
st = Stremio.torrentSource({ infoHash: p.torrent.infoHash, fileIdx: p.torrent.fileIdx, sources: p.torrent.sources, name: p.name, behaviorHints: st.behaviorHints });
|
|
208
|
+
if (!st.url) { U.toast('This one plays through a streaming server: Settings › Content › Streaming server', 6000); return; }
|
|
209
|
+
}
|
|
206
210
|
if (/^https:\/\/www\.youtube\.com\/watch\?v=[A-Za-z0-9_-]{11}$/.test(p.url || '')) { st._yt = true; st.ytPage = p.url; } // (a YouTube video: through the service again)
|
|
207
211
|
resumeSources(startPlayback(st, p.id, p.name, how), p.id);
|
|
208
212
|
}
|
|
@@ -321,7 +325,7 @@
|
|
|
321
325
|
if (!panel || (' ' + body.className + ' ').indexOf(' series-body ') === -1) { return; }
|
|
322
326
|
var sr = s.getBoundingClientRect(), br = body.getBoundingClientRect();
|
|
323
327
|
if (!(br.height > 0)) { return; }
|
|
324
|
-
var top = Math.round(br.bottom - sr.top) + PANEL_GAP;
|
|
328
|
+
var top = Math.round((br.bottom - sr.top) / U.zoom()) + PANEL_GAP; // (measured on screen, zoomed)
|
|
325
329
|
panel.style.top = Math.max(PANEL_MIN, Math.min(PANEL_MAX, top)) + 'px';
|
|
326
330
|
}
|
|
327
331
|
|
|
@@ -724,7 +728,7 @@
|
|
|
724
728
|
function fileStreams(v) {
|
|
725
729
|
return v.streams.map(function (st) {
|
|
726
730
|
var s = Object.assign({ name: fileLabel(v) }, st);
|
|
727
|
-
return Stremio.
|
|
731
|
+
return Stremio.torrentSource(Stremio.youTubeSource(s));
|
|
728
732
|
});
|
|
729
733
|
}
|
|
730
734
|
|
|
@@ -1395,6 +1399,8 @@
|
|
|
1395
1399
|
uncached: function (a, b) { return (a.info.cache === -1 ? 1 : 0) - (b.info.cache === -1 ? 1 : 0); },
|
|
1396
1400
|
kind: function (a, b) { return (KIND_SCORE[b.info.kind] || 0) - (KIND_SCORE[a.info.kind] || 0); },
|
|
1397
1401
|
hdr: function (a, b) { return (b.info.hdr ? 1 : 0) - (a.info.hdr ? 1 : 0); },
|
|
1402
|
+
// (only two sources that both state theirs: the others keep their order)
|
|
1403
|
+
seeds: function (a, b) { return (a.info.seeds >= 0 && b.info.seeds >= 0) ? b.info.seeds - a.info.seeds : 0; },
|
|
1398
1404
|
addon: function (a, b) { return (a.st._addonRank || 0) - (b.st._addonRank || 0); },
|
|
1399
1405
|
// each source's place in its addon's own list (an addon's first ties with another's
|
|
1400
1406
|
// first): a plain key, so it orders consistently wherever it sits among the criteria
|
|
@@ -1464,7 +1470,13 @@
|
|
|
1464
1470
|
// again), as the player takes a source, or null. total: how many it could try in all.
|
|
1465
1471
|
// avoid: [{ addon, tag }] — an addon's links through a debrid service that sent an account's
|
|
1466
1472
|
// notice (access refused, a limit: the player's noticeOf): they'd say the same.
|
|
1473
|
+
// Torrent sources' links from the streaming server set now (Settings may have changed it since
|
|
1474
|
+
// they came: none when it's off).
|
|
1475
|
+
function serverNow(list) {
|
|
1476
|
+
(list || []).forEach(function (st) { if (st && st.infoHash) { Stremio.torrentSource(st); } });
|
|
1477
|
+
}
|
|
1467
1478
|
function nextSourceFor(tried, avoid) {
|
|
1479
|
+
serverNow(_d && _d.streams);
|
|
1468
1480
|
var rows = (_d && _d.streams) ? filterRows(rankRowsFor(_d.streams)) : [], seen = {}, all = []; // (within the filter: Addon ▾ / Resolution ▾)
|
|
1469
1481
|
rows.forEach(function (r) {
|
|
1470
1482
|
var st = r.st, id = Stremio.sourceId(st), dt = Stremio.debridOf ? Stremio.debridOf(st) : null;
|
|
@@ -1847,6 +1859,7 @@
|
|
|
1847
1859
|
// before, 4K, 1080p, …) starts with its heading.
|
|
1848
1860
|
var heading = streamsHeading();
|
|
1849
1861
|
if (heading) { listScroll.appendChild(U.el('div', 'stream-for', heading)); }
|
|
1862
|
+
serverNow(_d.streams); // (a page kept from before: its torrents follow the streaming server set now)
|
|
1850
1863
|
var list = _d.streams, keepAt = -1, rows = rankRowsFor(list), lastGroup = null;
|
|
1851
1864
|
// Direct links only (Settings › Source ranking): the sources the TV can't play left out
|
|
1852
1865
|
// (torrents, links it can't open); where-to-watch apps stay (they open)
|
|
@@ -1915,10 +1928,11 @@
|
|
|
1915
1928
|
if (row && row.sized) { lines.push('📏 Outside your size limits: ' + row.sized + ' (Settings › Playback › Source ranking)'); }
|
|
1916
1929
|
if (row && row.blocked) { lines.push('⚠ ' + row.blocked + ' isn’t allowed (Settings › Playback › Formats)'); }
|
|
1917
1930
|
if (row && row.bad.length) { lines.push('⚠ Marked “Not working” on this TV: ' + row.bad.join(', ') + ' (Settings › Playback › Formats)'); }
|
|
1931
|
+
if (stream._torrent) { lines.push('🧲 A torrent, through your streaming server (Settings › Content › Streaming server)'); }
|
|
1918
1932
|
if (opens) {
|
|
1919
1933
|
lines.push('▶ OK opens ' + (opens.data ? 'it in ' : '') + opens.app.name);
|
|
1920
1934
|
} else if (!playable) {
|
|
1921
|
-
if (stream.infoHash) { lines.push('— torrent
|
|
1935
|
+
if (stream.infoHash) { lines.push('— torrent: plays through a streaming server (Settings › Content › Streaming server)'); }
|
|
1922
1936
|
else if (stream.externalUrl) {
|
|
1923
1937
|
lines.push('— another app' + ((typeof TVApps !== 'undefined' && TVApps.available())
|
|
1924
1938
|
? ': add it in Settings › Content › Streaming apps to open it from here' : ' (not playable here)'));
|
|
@@ -1936,7 +1950,7 @@
|
|
|
1936
1950
|
}
|
|
1937
1951
|
node.__onselect = function () {
|
|
1938
1952
|
if (opens) { openWhere(opens); return; }
|
|
1939
|
-
if (!playable) { U.toast('This source can’t play directly on the TV.'); return; }
|
|
1953
|
+
if (!playable) { U.toast(stream.infoHash ? 'Torrents play through a streaming server: Settings › Content › Streaming server' : 'This source can’t play directly on the TV.', stream.infoHash ? 6000 : undefined); return; }
|
|
1940
1954
|
startPlayback(stream, _d.streamId, _d.streamLabel || _d.meta.name, howHere());
|
|
1941
1955
|
};
|
|
1942
1956
|
return node;
|
|
@@ -1985,6 +1999,7 @@
|
|
|
1985
1999
|
// The player's "Select source" list: each source with the request headers it needs
|
|
1986
2000
|
// (IPTV), so switching to it sends its own (or none).
|
|
1987
2001
|
function playableSources(list) {
|
|
2002
|
+
serverNow(list || _d.streams);
|
|
1988
2003
|
return (list || _d.streams || []).filter(Stremio.isPlayable).map(function (st) {
|
|
1989
2004
|
var sz = Stremio.streamSize(st);
|
|
1990
2005
|
var desc = Stremio.streamDescription(st).replace(/\n/g, ' ');
|
|
@@ -2001,6 +2016,10 @@
|
|
|
2001
2016
|
|
|
2002
2017
|
// how (optional): { fromStart } — Play from start (Continue Watching): from 0:00.
|
|
2003
2018
|
function startPlayback(stream, videoId, label, how) {
|
|
2019
|
+
if (stream && stream.infoHash) { // (a torrent: its link from the streaming server set now)
|
|
2020
|
+
Stremio.torrentSource(stream);
|
|
2021
|
+
if (!stream.url) { U.toast('Torrents play through a streaming server: Settings › Content › Streaming server', 6000); return null; }
|
|
2022
|
+
}
|
|
2004
2023
|
// VOD changes progress/watched state. Live playback does not, and keeping Home
|
|
2005
2024
|
// untouched lets us restore the exact originating card and scroll position.
|
|
2006
2025
|
if (!(_d && _d.exitToBack)) { markHomeDirty(); }
|
|
@@ -2015,8 +2034,10 @@
|
|
|
2015
2034
|
var titleId = _d && _d.id, series = _d && _d.type === 'series', vtype = _d && _d.type;
|
|
2016
2035
|
var remembers = !!(_d && !_d.live);
|
|
2017
2036
|
// (also for another source picked in the player: what's learned is that one's)
|
|
2018
|
-
|
|
2019
|
-
|
|
2037
|
+
// how.converted: played converted by the streaming server, so its formats aren't learned
|
|
2038
|
+
// (they didn't play on the TV); Played before and the kept source still are.
|
|
2039
|
+
function learnFor(st, how) {
|
|
2040
|
+
var info = Stremio.streamInfo(st), fkeys = (how && how.converted) ? [] : Stremio.formatKeys(info);
|
|
2020
2041
|
return {
|
|
2021
2042
|
// (private viewing: nothing about this playback remembered or forgotten — its own
|
|
2022
2043
|
// flag, set when it started, see player-store; what plays on this TV is still learned)
|
|
@@ -2039,7 +2060,7 @@
|
|
|
2039
2060
|
if (_d) { _d.direct = false; _d.directList = false; _d.directFailed = false; } // (only this once: played again from the list, back to the list)
|
|
2040
2061
|
var onExit = function (how) {
|
|
2041
2062
|
// (after CH+/CH−, back on the card of the channel you ended on)
|
|
2042
|
-
if (exitToBack) {
|
|
2063
|
+
if (exitToBack) { var ch = _d.channels ? _d.channels.list[_d.channels.idx] : null; App.returnFromLive(ch ? ch.card : null, ch ? ch.meta.id : null); return; }
|
|
2043
2064
|
// played straight away (a single YouTube video): back where it was opened from
|
|
2044
2065
|
if (direct && !page.directFailed && _d === page) { App.back(); return; } // (Home redrawn if Continue Watching changed)
|
|
2045
2066
|
// a channel's video played straight away: back on the channel's videos, on it
|
|
@@ -2199,13 +2220,20 @@
|
|
|
2199
2220
|
}
|
|
2200
2221
|
|
|
2201
2222
|
// Load a specific episode's sources and auto-play the first playable one.
|
|
2223
|
+
// The next / previous episode (from the player): its sources asked for, the best like the
|
|
2224
|
+
// last one played. An answer once you've left the page, or asked for another episode since
|
|
2225
|
+
// (_epGen), does nothing: no playback, no page drawn, no spinner change, no note.
|
|
2226
|
+
var _epGen = 0;
|
|
2202
2227
|
function playEpisode(v) {
|
|
2228
|
+
var still = pageNow(), gen = ++_epGen;
|
|
2229
|
+
function current() { return still() && gen === _epGen; }
|
|
2203
2230
|
_d.mode = 'streams';
|
|
2204
2231
|
_d.streamId = v.id;
|
|
2205
2232
|
_d.season = (v.season === undefined ? 1 : v.season);
|
|
2206
2233
|
_d.streamLabel = epName(v);
|
|
2207
2234
|
U.spinner(true);
|
|
2208
2235
|
Stremio.getStreams(addonType(), v.id).then(function (list) {
|
|
2236
|
+
if (!current()) { return; }
|
|
2209
2237
|
_d.streams = sortStreams(Stremio.dedupeStreams(list));
|
|
2210
2238
|
U.spinner(false);
|
|
2211
2239
|
var pick = pickLikeLast(_d.id, _d.streams);
|
|
@@ -2215,7 +2243,7 @@
|
|
|
2215
2243
|
return;
|
|
2216
2244
|
}
|
|
2217
2245
|
startPlayback(pick, v.id, _d.streamLabel);
|
|
2218
|
-
}, function () { U.spinner(false); U.toast('Failed to load that episode'); });
|
|
2246
|
+
}, function () { if (!current()) { return; } U.spinner(false); U.toast('Failed to load that episode'); });
|
|
2219
2247
|
}
|
|
2220
2248
|
|
|
2221
2249
|
// ----- binge: the next episode plays a source like the one you've been watching -----
|
package/js/view-discover.js
CHANGED
|
@@ -334,7 +334,7 @@
|
|
|
334
334
|
}
|
|
335
335
|
function rowHeight() {
|
|
336
336
|
var els = _dv.cardEls, c = cols();
|
|
337
|
-
return (els.length > c) ? (els[c].getBoundingClientRect().top - els[0].getBoundingClientRect().top) : 0;
|
|
337
|
+
return (els.length > c) ? (els[c].getBoundingClientRect().top - els[0].getBoundingClientRect().top) / U.zoom() : 0; // (in the grid's own pixels)
|
|
338
338
|
}
|
|
339
339
|
function drawGrid() {
|
|
340
340
|
var grid = gridEl();
|
package/js/view-guide.js
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
an edge, never back before the current half hour for one still on), ↑ / ↓ the channel above /
|
|
8
8
|
below, at the same time. Home's details strip describes the focused program; OK on what's on
|
|
9
9
|
now plays the channel, on another says what it is. A guide-only channel's (tvg-type="guide":
|
|
10
|
-
watched elsewhere) any program: the film looked up in your addons (findInAddons).
|
|
10
|
+
watched elsewhere) any program: the film looked up in your addons (findInAddons). Hold OK on
|
|
11
|
+
a program: Watch now, the channel's favorites, Find in your addons (hold). */
|
|
11
12
|
(function (V) {
|
|
12
13
|
'use strict';
|
|
13
14
|
function scr() { return V.scr.apply(null, arguments); } // core
|
|
15
|
+
function pickList() { return V.pickList.apply(null, arguments); } // core
|
|
16
|
+
function refreshAddonRows() { return V.refreshAddonRows.apply(null, arguments); } // home
|
|
14
17
|
|
|
15
18
|
var SPAN = 150 * 60, STEP = 30 * 60, ROWS = 10, AHEAD = 10;
|
|
16
19
|
var _g = null; // the grid on screen: { el, start, chans, lists: { id: programs }, asked: { id: 1 }, top, nodes: [row: cells], box }
|
|
@@ -165,6 +168,7 @@
|
|
|
165
168
|
wait.__t = want && want.id === c.id && typeof want.t === 'number' ? want.t : Math.max(now, g.start);
|
|
166
169
|
wait.__meta = { name: c.name, releaseInfo: 'Reading its TV guide…' };
|
|
167
170
|
hooks(wait, -1);
|
|
171
|
+
wait.__onlongpress = function () { hold(g, r, null); };
|
|
168
172
|
track.appendChild(wait);
|
|
169
173
|
nodes.push(wait);
|
|
170
174
|
if (want && want.id === c.id) { found = wait; }
|
|
@@ -177,6 +181,7 @@
|
|
|
177
181
|
n.__meta = { name: p.title, releaseInfo: c.name + ' · ' + EPG.dayTime(p.start) + '–' + EPG.hhmm(p.stop) + (c.guideOnly ? ' · OK: find it in your addons' : ''), description: p.desc, picture: p.picture };
|
|
178
182
|
n.__p = p; n.__i = i;
|
|
179
183
|
n.__onselect = function () { pick(g, r, p); };
|
|
184
|
+
n.__onlongpress = function () { hold(g, r, p); };
|
|
180
185
|
hooks(n, i);
|
|
181
186
|
track.appendChild(n);
|
|
182
187
|
nodes.push(n);
|
|
@@ -301,6 +306,23 @@
|
|
|
301
306
|
(p.stop <= now ? ' (already shown)' : ''), 8000);
|
|
302
307
|
}
|
|
303
308
|
|
|
309
|
+
// Hold OK on a program: ▶ Watch now (what's on now, a channel that plays), ★ Add to / Remove
|
|
310
|
+
// from favorites (its channel), Find in your addons (that program, as a guide-only channel's
|
|
311
|
+
// OK). p: null for a channel whose guide hasn't come (its favorites only).
|
|
312
|
+
function hold(g, r, p) {
|
|
313
|
+
var c = g.chans[r], now = Date.now() / 1000, fav = IPTV.isFavorite(c.id), items = [];
|
|
314
|
+
if (p && !c.guideOnly && p.start <= now && now < p.stop) { items.push(['play', '▶ Watch now']); }
|
|
315
|
+
items.push(['fav', fav ? '★ Remove from favorites' : '★ Add to favorites']);
|
|
316
|
+
if (p && p.title) { items.push(['find', '🔍 Find in your addons']); }
|
|
317
|
+
pickList(c.name + (p && p.title ? ' · ' + p.title : ''), items, items[0][0], function (v) {
|
|
318
|
+
if (v === 'play') { pick(g, r, p); return; }
|
|
319
|
+
if (v === 'find') { findInAddons(p); return; }
|
|
320
|
+
IPTV.setFavorite(c.id, !fav);
|
|
321
|
+
U.toast(fav ? 'Removed from favorites' : 'Added to favorites: ★ Favorites, first on the IPTV and Guide tabs', 3000);
|
|
322
|
+
refreshAddonRows('local:iptv');
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
304
326
|
// A guide-only channel's program: a film found by the guide's original title and year (else
|
|
305
327
|
// its title) opens its page with your addons' sources, as Discover does; a show, or a film
|
|
306
328
|
// without one sure match, opens Search with its name.
|
|
@@ -328,5 +350,22 @@
|
|
|
328
350
|
Stremio.findFilm({ title: p.title, orig: p.orig, year: p.year }, cancel).then(done, function () { done(null); });
|
|
329
351
|
}
|
|
330
352
|
|
|
331
|
-
|
|
353
|
+
// Back on the Guide from a channel played from it (BACK, or its stream gave up), after CH+ /
|
|
354
|
+
// CH−: the hours at the present again, the row of the channel ended on in view (read if it
|
|
355
|
+
// wasn't), and what's on now on it. Returns that cell; null when the Guide isn't on screen or
|
|
356
|
+
// the channel isn't on it.
|
|
357
|
+
function guideBackOn(id) {
|
|
358
|
+
var g = _g, r = -1;
|
|
359
|
+
if (!id || !g || !g.box || !document.documentElement.contains(g.box)) { return null; }
|
|
360
|
+
for (var i = 0; i < g.chans.length; i++) { if (g.chans[i].id === id) { r = i; break; } }
|
|
361
|
+
if (r < 0) { return null; }
|
|
362
|
+
var now = Date.now() / 1000;
|
|
363
|
+
g.start = floorStep(now); g.live = true;
|
|
364
|
+
if (r < g.top) { g.top = r; } else if (r >= g.top + ROWS) { g.top = r - ROWS + 1; }
|
|
365
|
+
var n = draw(g, { id: id, t: now });
|
|
366
|
+
ensure(g).then(function (got) { if (got) { refresh(g); } }); // (its programs, when they weren't read: then onto what's on)
|
|
367
|
+
return n;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
V.guideGrid = guideGrid; V.guideBackOn = guideBackOn;
|
|
332
371
|
})(ViewParts);
|
package/js/view-home.js
CHANGED
|
@@ -164,18 +164,32 @@
|
|
|
164
164
|
// reordered): reload just its rows; every other row keeps what it has, with no request.
|
|
165
165
|
// url: the addon ('local:iptv' for IPTV); none: nothing to fetch (only the set / order
|
|
166
166
|
// of rows changed). Calls before the reload runs add up.
|
|
167
|
+
// An addon Home had no rows from (IPTV read after your addons, a first playlist) is one too:
|
|
168
|
+
// its rows asked for, its tabs added to the menu when they come.
|
|
167
169
|
var _reloadOnly = null; // { addon url: 1 } for the next reload, else a full one
|
|
168
|
-
var _homeAddons = {}; // { addon url: 1 } the addons Home's last load had catalogs from
|
|
169
170
|
function refreshAddonRows(url) {
|
|
170
171
|
if (_homeCatalogs === null) { clearCatalogCache(); return; } // nothing loaded yet: a full load anyway
|
|
171
|
-
// an addon Home didn't load rows from (the first playlist added: IPTV): a full load, its tab with it
|
|
172
|
-
if (url && !_homeAddons[url]) { clearCatalogCache(); return; }
|
|
173
172
|
_reloadOnly = _reloadOnly || {};
|
|
174
173
|
if (url) { _reloadOnly[url] = 1; }
|
|
175
174
|
_homeDirty = true;
|
|
176
175
|
if (_current() === scr('home') && !isModalOpen() && !Player.isActive()) { loadHomeCatalogs(); }
|
|
177
176
|
}
|
|
178
177
|
|
|
178
|
+
// Auto-refresh (Settings › Content › IPTV): every minute, whether the playlists are due —
|
|
179
|
+
// their oldest download (a saved copy's own date) and the last try both that long ago — then
|
|
180
|
+
// all of them and their guides read again, as Reload does, and their rows with them. Not
|
|
181
|
+
// while something plays or StremioK is in the background: at the next minute after.
|
|
182
|
+
var _iptvTriedAt = 0, _iptvReading = false;
|
|
183
|
+
function iptvRefreshTick() {
|
|
184
|
+
var h = Stremio.iptvRefreshEvery ? Stremio.iptvRefreshEvery() : 0;
|
|
185
|
+
if (!h || _iptvReading || document.hidden || Player.isActive()) { return; }
|
|
186
|
+
var due = h * 3600000, now = Date.now(), at = Stremio.iptvReadAt();
|
|
187
|
+
if (at === Infinity || now - at < due || now - _iptvTriedAt < due) { return; }
|
|
188
|
+
_iptvTriedAt = now; _iptvReading = true;
|
|
189
|
+
Stremio.iptvReload().then(function () { _iptvReading = false; refreshAddonRows('local:iptv'); }, function () { _iptvReading = false; });
|
|
190
|
+
}
|
|
191
|
+
setInterval(iptvRefreshTick, 60000);
|
|
192
|
+
|
|
179
193
|
// The Home rows are also saved on the TV, trimmed to what a card and the details strip
|
|
180
194
|
// use, so a launch shows the last rows at once and refreshes them in the background
|
|
181
195
|
// (as the 15-minute refresh does). Saved rows are only used with the same addons.
|
|
@@ -1179,7 +1193,10 @@
|
|
|
1179
1193
|
}
|
|
1180
1194
|
|
|
1181
1195
|
// Switch the active home tab (from the shared top menu) and show it.
|
|
1182
|
-
function setHomeTab(key) { _homeTab = key || _homeTab; _focusFirstCard = 'new'; }
|
|
1196
|
+
function setHomeTab(key) { _homeTab = key || _homeTab; _focusFirstCard = 'new'; _wantTab = null; }
|
|
1197
|
+
// A tab asked for whose rows are still coming (a first playlist's IPTV tab): shown when they
|
|
1198
|
+
// come, if Home is still on the tab it showed instead. { key, instead }
|
|
1199
|
+
var _wantTab = null;
|
|
1183
1200
|
|
|
1184
1201
|
// prevFocus: a Home card to put focus back on after the rebuild (the card a title
|
|
1185
1202
|
// was opened from). Otherwise the card currently focused on Home is kept, so rows
|
|
@@ -1206,7 +1223,9 @@
|
|
|
1206
1223
|
// Keep the active tab valid for what's actually available.
|
|
1207
1224
|
var avail = availableTabs();
|
|
1208
1225
|
if (!_homeTab || !avail.some(function (t) { return t.key === _homeTab; })) {
|
|
1226
|
+
var asked = _homeTab;
|
|
1209
1227
|
_homeTab = avail.length ? avail[0].key : 'movie';
|
|
1228
|
+
if (asked && (_homeLoading || _reloadOnly)) { _wantTab = { key: asked, instead: _homeTab }; }
|
|
1210
1229
|
}
|
|
1211
1230
|
|
|
1212
1231
|
s.appendChild(buildTopMenu(_homeTab));
|
|
@@ -1416,12 +1435,12 @@
|
|
|
1416
1435
|
_homeLoading = false;
|
|
1417
1436
|
_homeFirstLoad = false;
|
|
1418
1437
|
if (first) { U.spinner(false); }
|
|
1419
|
-
|
|
1438
|
+
// (rows asked for meanwhile: the IPTV ones, read after the addons; unless the redraw
|
|
1439
|
+
// that follows has asked for them already)
|
|
1440
|
+
if (_reloadOnly && onHome()) { setTimeout(function () { if (_reloadOnly) { loadHomeCatalogs(); } }, 0); }
|
|
1420
1441
|
}
|
|
1421
1442
|
function onHome() { return _current() === s && !isModalOpen() && !Player.isActive(); }
|
|
1422
1443
|
Stremio.catalogsForHome().then(function (cats) {
|
|
1423
|
-
_homeAddons = {};
|
|
1424
|
-
cats.forEach(function (cat) { if (cat.addon && cat.addon.url) { _homeAddons[cat.addon.url] = 1; } });
|
|
1425
1444
|
var acc = cats.map(function (cat) { return copyCat({ name: cat.name, type: cat.type, yt: isYouTubeCat(cat) }, []); });
|
|
1426
1445
|
var arrived = cats.map(function () { return false; }), shown = 0, partialTimer = null;
|
|
1427
1446
|
// First load: publish each row as soon as it arrives, in its own place (a slower one above
|
|
@@ -1458,11 +1477,16 @@
|
|
|
1458
1477
|
U.settleAll(tasks, 3).then(function () {
|
|
1459
1478
|
_homeCatalogs = splitOtherCatalogs(acc).filter(function (c) { return c.metas && c.metas.length; });
|
|
1460
1479
|
done();
|
|
1480
|
+
var want = _wantTab;
|
|
1481
|
+
_wantTab = null;
|
|
1482
|
+
if (want && _homeTab === want.instead && onHome() && availableTabs().some(function (t) { return t.key === want.key; })) {
|
|
1483
|
+
setHomeTab(want.key);
|
|
1484
|
+
}
|
|
1461
1485
|
saveHomeCache();
|
|
1462
1486
|
// The tab on screen is redrawn only if what it shows changed: redrawing rebuilds
|
|
1463
1487
|
// every card (and reloads its artwork), which shows as a flicker. Continue
|
|
1464
1488
|
// Watching doesn't come from catalogs at all. Other tabs rebuild when opened.
|
|
1465
|
-
var same = (before !== null && shownSig() === before) || ownTabStill();
|
|
1489
|
+
var same = (before !== null && shownSig() === before && menuSig() === _renderedMenu) || ownTabStill();
|
|
1466
1490
|
var keep = {};
|
|
1467
1491
|
if (same && _tabRows[_homeTab]) { keep[_homeTab] = _tabRows[_homeTab]; }
|
|
1468
1492
|
_tabRows = keep;
|
|
@@ -1473,6 +1497,7 @@
|
|
|
1473
1497
|
});
|
|
1474
1498
|
}, function () {
|
|
1475
1499
|
done();
|
|
1500
|
+
_wantTab = null;
|
|
1476
1501
|
if (first) { U.toast('Failed to load addons. Check network / Settings.'); }
|
|
1477
1502
|
});
|
|
1478
1503
|
}
|