stremiok 2.0.0 → 2.1.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/README.md +4 -2
- package/config.xml +1 -1
- package/css/style.css +10 -2
- package/index.html +31 -31
- package/js/downloads.js +17 -5
- package/js/hubpreview.js +15 -5
- package/js/iptv.js +4 -4
- package/js/player-controls.js +5 -0
- package/js/player-core.js +92 -19
- package/js/player-lang.js +1 -1
- package/js/player-quality.js +15 -7
- package/js/player-store.js +7 -3
- package/js/player-subs.js +7 -3
- package/js/player.js +1 -1
- package/js/stremio-account.js +3 -0
- package/js/stremio.js +87 -22
- package/js/trakt.js +60 -9
- package/js/util.js +17 -2
- package/js/view-core.js +11 -0
- package/js/view-detail.js +5 -4
- package/js/view-home.js +27 -10
- package/js/view-settings.js +67 -45
- package/js/youtube.js +1 -3
- package/js/ytplay.js +10 -1
- package/package.json +1 -1
- package/service/index.js +280 -74
- package/service/preview.js +14 -4
package/js/view-home.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
{ key: 'tv', icon: '📡', label: 'IPTV' },
|
|
29
29
|
{ key: 'radio', icon: '📻', label: 'Radio' },
|
|
30
30
|
{ key: 'sport', icon: 'âš½', label: 'Sports' },
|
|
31
|
+
{ key: 'youtube', icon: '📹', label: 'YouTube' }, // (StremioK's YouTube catalogs, an addon's YouTube / channel ones: see isYouTubeCat)
|
|
31
32
|
{ key: 'local', icon: '💾', label: 'Local' } // (a USB drive plugged in: see LocalMedia)
|
|
32
33
|
];
|
|
33
34
|
function hasLocal() { return typeof LocalMedia !== 'undefined' && LocalMedia.present(); }
|
|
@@ -46,10 +47,18 @@
|
|
|
46
47
|
});
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
//
|
|
50
|
-
//
|
|
50
|
+
// YouTube's tab: StremioK's own YouTube catalogs (whatever type you gave one: "Podcasts"),
|
|
51
|
+
// and another addon's YouTube or channel catalogs (YouTubio's "YouTube", Stremio's "channel").
|
|
52
|
+
function isYouTubeCat(c) {
|
|
53
|
+
var m = c.addon && c.addon.manifest;
|
|
54
|
+
return !!(m && m.id === 'org.stremiok.youtube') || /^(youtube|channel)$/i.test('' + c.type);
|
|
55
|
+
}
|
|
56
|
+
// Which cached catalogs belong to a tab. Sports, radio and YouTube have dedicated tabs;
|
|
57
|
+
// IPTV is the catch-all for every other live source (tv, …).
|
|
51
58
|
function catsForTab(key) {
|
|
52
59
|
return (_homeCatalogs || []).filter(function (c) {
|
|
60
|
+
if (key === 'youtube') { return isYouTubeCat(c); }
|
|
61
|
+
if (isYouTubeCat(c)) { return false; }
|
|
53
62
|
if (key === 'movie') { return c.type === 'movie'; }
|
|
54
63
|
if (key === 'series') { return c.type === 'series'; }
|
|
55
64
|
if (key === 'sport') { return c.type === 'sport'; }
|
|
@@ -967,16 +976,18 @@
|
|
|
967
976
|
showModal(overlay, details || watched);
|
|
968
977
|
}
|
|
969
978
|
|
|
970
|
-
// Local progress merged with Trakt playback (when connected), deduped, newest first
|
|
979
|
+
// Local progress merged with Trakt playback (when connected), deduped, newest first; a
|
|
980
|
+
// place kept here that Trakt has you watching past since (another TV) left out.
|
|
971
981
|
// One card per show: its furthest episode (highest season, then episode), placed by
|
|
972
982
|
// the show's most recent activity. The others ride along as _dupes so Remove clears
|
|
973
983
|
// the whole show.
|
|
974
984
|
// byActivity: the most recent activity first, whatever order you gave the row (Move): the
|
|
975
985
|
// home bar's tiles (previewTiles).
|
|
976
986
|
function continueEntries(byActivity) {
|
|
977
|
-
var local = Player.continueList();
|
|
987
|
+
var local = Player.continueList(), traktOn = typeof Trakt !== 'undefined' && Trakt.isConnected();
|
|
988
|
+
if (traktOn && Trakt.watchedPast) { local = local.filter(function (e) { return !Trakt.watchedPast(e); }); }
|
|
978
989
|
var merged = local.slice();
|
|
979
|
-
if (
|
|
990
|
+
if (traktOn) {
|
|
980
991
|
var seen = {};
|
|
981
992
|
local.forEach(function (e) { seen[e.type + ':' + e.id] = 1; });
|
|
982
993
|
Trakt.continueList().forEach(function (e) {
|
|
@@ -1272,6 +1283,8 @@
|
|
|
1272
1283
|
if (typeof IPTV !== 'undefined' && IPTV.onProgress) {
|
|
1273
1284
|
IPTV.onProgress(function (n) { if (_homeFirstLoad) { U.spinner(true, channelsText(n)); } });
|
|
1274
1285
|
}
|
|
1286
|
+
// The IPTV playlists read after your addons loaded (they aren't waited for): their rows join.
|
|
1287
|
+
if (typeof Stremio !== 'undefined' && Stremio.onIptvReady) { Stremio.onIptvReady(function () { refreshAddonRows('local:iptv'); }); }
|
|
1275
1288
|
var PARTIAL_DELAY = 150; // coalesces catalogs that arrive together into one redraw
|
|
1276
1289
|
function loadHomeCatalogs() {
|
|
1277
1290
|
if (_homeLoading) { return; }
|
|
@@ -1295,19 +1308,21 @@
|
|
|
1295
1308
|
_homeLoading = false;
|
|
1296
1309
|
_homeFirstLoad = false;
|
|
1297
1310
|
if (first) { U.spinner(false); }
|
|
1311
|
+
if (_reloadOnly && onHome()) { setTimeout(loadHomeCatalogs, 0); } // (rows asked for meanwhile: the IPTV ones, read after the addons)
|
|
1298
1312
|
}
|
|
1299
1313
|
function onHome() { return _current() === s && !isModalOpen() && !Player.isActive(); }
|
|
1300
1314
|
Stremio.catalogsForHome().then(function (cats) {
|
|
1301
1315
|
var acc = cats.map(function (cat) { return { name: cat.name, type: cat.type, metas: [] }; });
|
|
1302
1316
|
var arrived = cats.map(function () { return false; }), shown = 0, partialTimer = null;
|
|
1303
|
-
// First load: publish
|
|
1304
|
-
// when
|
|
1317
|
+
// First load: publish each row as soon as it arrives, in its own place (a slower one above
|
|
1318
|
+
// it held by a "Loading…" placeholder, so nothing jumps when it comes), and redraw when
|
|
1319
|
+
// that changes the tab on screen.
|
|
1305
1320
|
function partial() {
|
|
1306
|
-
var n =
|
|
1307
|
-
while (n < arrived.length && arrived[n]) { n++; }
|
|
1321
|
+
var n = arrived.filter(Boolean).length, last = arrived.lastIndexOf(true);
|
|
1308
1322
|
if (!first || n <= shown) { return; }
|
|
1309
1323
|
shown = n;
|
|
1310
|
-
|
|
1324
|
+
var now = acc.slice(0, last + 1).map(function (c, i) { return arrived[i] ? c : { name: c.name, type: c.type, metas: [], loading: true }; });
|
|
1325
|
+
_homeCatalogs = splitOtherCatalogs(now).filter(function (c) { return c.loading || (c.metas && c.metas.length); });
|
|
1311
1326
|
if (partialTimer) { return; }
|
|
1312
1327
|
partialTimer = setTimeout(function () {
|
|
1313
1328
|
partialTimer = null;
|
|
@@ -1360,6 +1375,7 @@
|
|
|
1360
1375
|
function splitOtherCatalogs(list) {
|
|
1361
1376
|
var out = [];
|
|
1362
1377
|
list.forEach(function (c) {
|
|
1378
|
+
if (c.loading) { if (c.type !== 'other') { out.push({ name: c.name, type: c.type, metas: [], loading: true }); } return; } // (a placeholder: still loading)
|
|
1363
1379
|
if (c.type !== 'other') { out.push({ name: c.name, type: c.type, metas: c.metas.slice(0, ROW_CARDS) }); return; }
|
|
1364
1380
|
var movies = [], series = [];
|
|
1365
1381
|
c.metas.forEach(function (m) { (SERIES_NAME.test(m.name || '') ? series : movies).push(m); });
|
|
@@ -1379,6 +1395,7 @@
|
|
|
1379
1395
|
for (var i = from; i < to; i++) {
|
|
1380
1396
|
var c = cats[i];
|
|
1381
1397
|
var row = buildRow(c.name, c.metas.map(function (m) { return posterCard(m, c.type); }));
|
|
1398
|
+
if (c.loading) { row.className += ' row-loading'; row.appendChild(U.el('div', 'row-empty', 'Loading…')); } // (its place kept: nothing to focus)
|
|
1382
1399
|
rows.appendChild(row);
|
|
1383
1400
|
built.push(row);
|
|
1384
1401
|
}
|
package/js/view-settings.js
CHANGED
|
@@ -323,7 +323,7 @@
|
|
|
323
323
|
['Audio', 'Preferred and second-choice audio language', 'audio'],
|
|
324
324
|
['Subtitles', 'Default language, size, color, background, position, font', 'subtitles'],
|
|
325
325
|
['Skipping', 'Intros, recaps and previews, and the next episode at the credits: ask, or on their own after a countdown', 'skipping'],
|
|
326
|
-
['Seeking & buffering', 'How far â—€ â–¶ jump; a bigger buffer for slow connections', 'seeking'],
|
|
326
|
+
['Seeking & buffering', 'How far â—€ â–¶ jump; a bigger buffer for slow connections; reconnecting live channels', 'seeking'],
|
|
327
327
|
['Source ranking', 'How sources are ordered (played before, resolution, release type…), and your addons: their order, on or off', 'ranking'],
|
|
328
328
|
['Formats', 'Allowed resolutions, and what plays on this TV, learned as you watch', 'formats'],
|
|
329
329
|
['Private viewing', 'Play without anything recorded: no progress, Continue Watching, watched marks or Trakt', 'private']] },
|
|
@@ -470,6 +470,24 @@
|
|
|
470
470
|
});
|
|
471
471
|
return 'Gives: ' + (bits.length ? bits.join(' · ') : 'nothing StremioK uses') + (types.length ? ' · ' + types.slice(0, 8).join(', ') : '');
|
|
472
472
|
}
|
|
473
|
+
// An addon's logo tile: its initial (the first character, an emoji whole; "?" for one that
|
|
474
|
+
// didn't load; drawn by the CSS from data-initial, so the row's text is still its name),
|
|
475
|
+
// and over it the logo from its manifest (Stremio.addonLogo), loaded with the other
|
|
476
|
+
// artwork; one that can't load is taken away, leaving the initial.
|
|
477
|
+
function addonTile(m, url) {
|
|
478
|
+
var name = ('' + ((m && m.name) || '')).replace(/^\s+/, ''), c = name.charCodeAt(0);
|
|
479
|
+
var tile = U.el('div', 'addon-logo');
|
|
480
|
+
tile.setAttribute('data-initial', !m ? '?' : name ? name.slice(0, c >= 0xD800 && c <= 0xDBFF ? 2 : 1).toUpperCase() : 'A');
|
|
481
|
+
var src = Stremio.addonLogo ? Stremio.addonLogo(m, url) : null;
|
|
482
|
+
if (src) {
|
|
483
|
+
var img = document.createElement('img');
|
|
484
|
+
img.onload = function () { tile.className = 'addon-logo loaded'; };
|
|
485
|
+
img.onerror = function () { if (img.parentNode) { img.parentNode.removeChild(img); } tile.className = 'addon-logo'; };
|
|
486
|
+
tile.appendChild(img);
|
|
487
|
+
U.deferImage(img, src);
|
|
488
|
+
}
|
|
489
|
+
return tile;
|
|
490
|
+
}
|
|
473
491
|
function settingsAddons(wrap) {
|
|
474
492
|
wrap.appendChild(U.el('h2', null, 'Addons'));
|
|
475
493
|
// Home-network links from addons on the internet (see Stremio.blocksLocal)
|
|
@@ -489,16 +507,14 @@
|
|
|
489
507
|
var urls = Stremio.getAddonUrls(), want = null;
|
|
490
508
|
if (urls.length > 1) { wrap.appendChild(U.el('div', 'hint', 'â–² / â–¼ set the order: a title\'s details come from the highest addon that has them (its page says which), and the higher addon comes first where several answer.')); }
|
|
491
509
|
urls.forEach(function (url) {
|
|
492
|
-
var
|
|
493
|
-
var
|
|
494
|
-
|
|
495
|
-
var
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
down.__onselect = move(1, 'down');
|
|
499
|
-
var item = U.el('div', 'addon-item focusable playlist-item');
|
|
510
|
+
var off = !!(Stremio.isAddonOff && Stremio.isAddonOff(url)), kept = off && Stremio.savedManifest ? Stremio.savedManifest(url) : null;
|
|
511
|
+
var loaded = addons.find(function (a) { return a.url === url; }) || (kept ? { url: url, manifest: kept } : null); // (one that's off: its saved manifest)
|
|
512
|
+
var frow = U.el('div', 'feed-row playlist-row' + (off ? ' feed-off' : ''));
|
|
513
|
+
var btns = orderButtons(function (dir, ctl) { if (Stremio.moveAddon(url, dir)) { _addonFocus = { url: url, ctl: ctl }; refreshAddonRows(); renderSettings(); } });
|
|
514
|
+
var item = U.el('div', 'addon-item focusable playlist-item has-logo');
|
|
515
|
+
item.appendChild(addonTile(loaded && loaded.manifest, url));
|
|
500
516
|
var why = !loaded && Stremio.manifestProblem ? Stremio.manifestProblem(url) : ''; // "too large (over 2 MB)", "not an addon manifest"…
|
|
501
|
-
var nm = U.el('div', 'a-name', loaded ? (loaded.manifest.name || 'Addon') : '(failed to load' + (why ? ': ' + why : '') + ')');
|
|
517
|
+
var nm = U.el('div', 'a-name', (loaded ? (loaded.manifest.name || 'Addon') : off ? 'Addon' : '(failed to load' + (why ? ': ' + why : '') + ')') + (off ? ' (off)' : ''));
|
|
502
518
|
var origin = Stremio.addonOrigin ? Stremio.addonOrigin(url) : '', ob = originBadge(origin);
|
|
503
519
|
if (ob) { nm.appendChild(ob); }
|
|
504
520
|
item.appendChild(nm);
|
|
@@ -512,10 +528,10 @@
|
|
|
512
528
|
item.__onselect = function () {
|
|
513
529
|
addonActions(name, url);
|
|
514
530
|
};
|
|
515
|
-
if (urls.length > 1) {
|
|
531
|
+
if (urls.length > 1) { btns.forEach(function (b) { frow.appendChild(b); }); }
|
|
516
532
|
frow.appendChild(item);
|
|
517
533
|
wrap.appendChild(frow);
|
|
518
|
-
if (_addonFocus && _addonFocus.url === url) { want = _addonFocus.ctl
|
|
534
|
+
if (_addonFocus && _addonFocus.url === url) { want = btns[ctlIndex(_addonFocus.ctl)]; }
|
|
519
535
|
});
|
|
520
536
|
|
|
521
537
|
var addRow = U.el('div', 'addon-item focusable add-row');
|
|
@@ -731,9 +747,12 @@
|
|
|
731
747
|
var byNorm = {};
|
|
732
748
|
there.forEach(function (x) { byNorm[norm(x.url)] = x; });
|
|
733
749
|
var mine = Stremio.getAddonUrls(), mineNorm = mine.map(norm);
|
|
734
|
-
|
|
750
|
+
// Only what came from AIOManager is its to refresh or remove (am.synced: the ones a sync
|
|
751
|
+
// added); an addon you added yourself stays yours, even when AIOManager has it too.
|
|
752
|
+
function fromIt(u) { return am.synced.indexOf(norm(u)) !== -1; }
|
|
753
|
+
var shared = mine.filter(function (u) { var x = byNorm[norm(u)]; return fromIt(u) && x && x.enabled !== false; });
|
|
735
754
|
var add = there.filter(function (x) { return x.enabled !== false && mineNorm.indexOf(norm(x.url)) === -1; });
|
|
736
|
-
var gone = mine.filter(function (u) { var x = byNorm[norm(u)]; return
|
|
755
|
+
var gone = mine.filter(function (u) { var x = byNorm[norm(u)]; return fromIt(u) && (!x || x.enabled === false); });
|
|
737
756
|
var items = [];
|
|
738
757
|
if (shared.length) { items.push({ key: 'refresh', label: '↻ Refresh the ' + shared.length + ' addon' + (shared.length === 1 ? '' : 's') + ' you have from AIOManager' }); }
|
|
739
758
|
add.forEach(function (x, i) { items.push({ key: 'add:' + i, label: '+ Add · ' + U.drawable(x.name || shortHost(x.url)).slice(0, 50) + ' · ' + shortHost(x.url) }); });
|
|
@@ -741,18 +760,18 @@
|
|
|
741
760
|
var x = byNorm[norm(u)];
|
|
742
761
|
items.push({ key: 'rm:' + i, label: '− Remove · ' + (x && x.name ? U.drawable(x.name).slice(0, 50) + ' · ' : '') + shortHost(u), note: x ? 'off in AIOManager' : 'gone from AIOManager' });
|
|
743
762
|
});
|
|
744
|
-
function done(n) {
|
|
745
|
-
var
|
|
746
|
-
keptUrls.
|
|
763
|
+
function done(n, addedUrls) { // what came from it: as before, and what this sync added (still in your list)
|
|
764
|
+
var now = (addedUrls || []).map(norm);
|
|
765
|
+
var keptUrls = Stremio.getAddonUrls().filter(function (u) { return fromIt(u) || now.indexOf(norm(u)) !== -1; });
|
|
747
766
|
aiomSave({ synced: keptUrls.map(norm), at: Date.now(), count: there.length });
|
|
748
767
|
if (n) { clearCatalogCache(); refreshAddonRows(null); }
|
|
749
768
|
renderSettings();
|
|
750
769
|
}
|
|
751
770
|
if (!items.length) { U.toast('Nothing to sync: AIOManager has no addons StremioK can use', 4000); done(0); return; }
|
|
752
771
|
pickMany('Sync with AIOManager: tick what to do', items, { button: function (n) { return 'Apply ' + n; } }, function (keys) {
|
|
753
|
-
var added = 0, removed = 0,
|
|
772
|
+
var added = 0, removed = 0, addedUrls = [];
|
|
754
773
|
keys.forEach(function (k) {
|
|
755
|
-
if (k.indexOf('add:') === 0) { var got = Stremio.addAddon(add[+k.slice(4)].url); if (got) { added++; Stremio.setAddonOrigin(got, 'aiomanager'); } }
|
|
774
|
+
if (k.indexOf('add:') === 0) { var got = Stremio.addAddon(add[+k.slice(4)].url); if (got) { added++; addedUrls.push(got); Stremio.setAddonOrigin(got, 'aiomanager'); } }
|
|
756
775
|
else if (k.indexOf('rm:') === 0) { Stremio.removeAddon(gone[+k.slice(3)]); removed++; }
|
|
757
776
|
});
|
|
758
777
|
var refreshing = keys.indexOf('refresh') !== -1 ? shared.filter(function (u) { return Stremio.getAddonUrls().indexOf(u) !== -1; }) : [];
|
|
@@ -762,7 +781,7 @@
|
|
|
762
781
|
if (removed) { parts.push(removed + ' removed'); }
|
|
763
782
|
if (refreshing.length) { parts.push(refreshed + ' refreshed' + (refreshed < refreshing.length ? ' (' + (refreshing.length - refreshed) + ' didn’t answer)' : '')); }
|
|
764
783
|
U.toast('Synced: ' + (parts.join(', ') || 'nothing changed'), 4000);
|
|
765
|
-
done(added + removed + refreshed);
|
|
784
|
+
done(added + removed + refreshed, addedUrls);
|
|
766
785
|
});
|
|
767
786
|
});
|
|
768
787
|
}, function (e) {
|
|
@@ -888,10 +907,7 @@
|
|
|
888
907
|
renderSettings();
|
|
889
908
|
};
|
|
890
909
|
}
|
|
891
|
-
var
|
|
892
|
-
up.__onselect = move(-1, 'up');
|
|
893
|
-
var down = ctl(U.el('div', 'feed-btn focusable', 'â–¼'), 'down');
|
|
894
|
-
down.__onselect = move(1, 'down');
|
|
910
|
+
var btns = orderButtons(function (dir, name) { move(dir, name)(); }, ctl);
|
|
895
911
|
var item = ctl(U.el('div', 'addon-item focusable playlist-item'), 'item');
|
|
896
912
|
item.appendChild(U.el('div', 'a-name', label + (playlist.off ? ' (off)' : '')));
|
|
897
913
|
item.appendChild(U.el('div', 'a-url', U.redactUrl(playlist.url))); // (credentials masked)
|
|
@@ -903,8 +919,7 @@
|
|
|
903
919
|
_iptvFocus = { id: playlist.id, ctl: 'item' };
|
|
904
920
|
playlistActions(playlist, label);
|
|
905
921
|
};
|
|
906
|
-
frow.appendChild(
|
|
907
|
-
frow.appendChild(down);
|
|
922
|
+
btns.forEach(function (b) { frow.appendChild(b); });
|
|
908
923
|
frow.appendChild(item);
|
|
909
924
|
wrap.appendChild(frow);
|
|
910
925
|
rows.push({ p: playlist, info: info, problem: problem, idx: idx });
|
|
@@ -1090,15 +1105,12 @@
|
|
|
1090
1105
|
list.forEach(function (x) {
|
|
1091
1106
|
var frow = U.el('div', 'feed-row playlist-row' + (x.off ? ' feed-off' : ''));
|
|
1092
1107
|
function ctl(node, name) { node.setAttribute('data-pl', x.id); node.setAttribute('data-ctl', name); return node; }
|
|
1093
|
-
var
|
|
1094
|
-
up.__onselect = function () { if (YouTube.move(x.id, -1)) { ytChanged({ id: x.id, ctl: 'up' }); } };
|
|
1095
|
-
var down = ctl(U.el('div', 'feed-btn focusable', 'â–¼'), 'down');
|
|
1096
|
-
down.__onselect = function () { if (YouTube.move(x.id, 1)) { ytChanged({ id: x.id, ctl: 'down' }); } };
|
|
1108
|
+
var btns = orderButtons(function (dir, name) { if (YouTube.move(x.id, dir)) { ytChanged({ id: x.id, ctl: name }); } }, ctl);
|
|
1097
1109
|
var item = ctl(U.el('div', 'addon-item focusable playlist-item yt-catalog'), 'item');
|
|
1098
1110
|
item.appendChild(U.el('div', 'a-name', x.name + (x.off ? ' (off)' : '')));
|
|
1099
1111
|
item.appendChild(U.el('div', 'a-url', ytDescribe(x)));
|
|
1100
1112
|
item.__onselect = function () { ytActions(x); };
|
|
1101
|
-
|
|
1113
|
+
btns.forEach(function (b) { frow.appendChild(b); }); frow.appendChild(item);
|
|
1102
1114
|
wrap.appendChild(frow);
|
|
1103
1115
|
});
|
|
1104
1116
|
|
|
@@ -1347,14 +1359,17 @@
|
|
|
1347
1359
|
// unused fails the first time while it wakes up).
|
|
1348
1360
|
var ADDON_WAITS = [['0', 'Normal (12 s; sources 15, search 25)'], ['20', '20 seconds'], ['30', '30 seconds'], ['45', '45 seconds'], ['60', '60 seconds']];
|
|
1349
1361
|
var NEXT_CHOICES = [['0', 'Ask'], ['5', 'After 5 seconds'], ['10', 'After 10 seconds'], ['15', 'After 15 seconds'], ['30', 'After 30 seconds']];
|
|
1350
|
-
// Seeking & buffering: how far LEFT / RIGHT jump; a bigger buffer for slow connections
|
|
1362
|
+
// Seeking & buffering: how far LEFT / RIGHT jump; a bigger buffer for slow connections; how
|
|
1363
|
+
// often a live channel that drops is opened again.
|
|
1364
|
+
var RECONNECTS = [['off', 'Off'], ['few', 'A few times (3 in 5 minutes)'], ['always', 'Keep trying']];
|
|
1351
1365
|
function settingsSeeking(wrap) {
|
|
1352
1366
|
wrap.appendChild(U.el('h2', null, 'Seeking & buffering'));
|
|
1353
|
-
var store = { get: function () { var c = Player.playerCfg(); return { seek: '' + c.seek, buffer: c.buffer }; },
|
|
1354
|
-
set: function (cfg) { Player.setPlayerCfg({ seek: +cfg.seek || 10, buffer: cfg.buffer }); } };
|
|
1367
|
+
var store = { get: function () { var c = Player.playerCfg(); return { seek: '' + c.seek, buffer: c.buffer, reconnect: c.reconnect }; },
|
|
1368
|
+
set: function (cfg) { Player.setPlayerCfg({ seek: +cfg.seek || 10, buffer: cfg.buffer, reconnect: cfg.reconnect }); } };
|
|
1355
1369
|
wrap.appendChild(subPickRow('Seek step', [['5', '5 s'], ['10', '10 s'], ['15', '15 s'], ['30', '30 s']], 'seek', store));
|
|
1356
1370
|
wrap.appendChild(subPickRow('Buffer', [['normal', 'Normal (the TV’s own)'], ['more', 'More'], ['most', 'Most']], 'buffer', store));
|
|
1357
|
-
wrap.appendChild(
|
|
1371
|
+
wrap.appendChild(subPickRow('Reconnect live channels', RECONNECTS, 'reconnect', store));
|
|
1372
|
+
wrap.appendChild(U.el('div', 'hint', 'Seek step: how far LEFT / RIGHT jump (FF / REW jump three steps). Buffer: More or Most starts a little slower but stops less on a slow connection, from the next video you play. Reconnect: a live channel that drops opens again; Keep trying waits a little longer each time (BACK stops it).'));
|
|
1358
1373
|
focusSettings();
|
|
1359
1374
|
}
|
|
1360
1375
|
function settingsSkipping(wrap) {
|
|
@@ -1962,7 +1977,20 @@
|
|
|
1962
1977
|
var SOURCE_ROWS = { prefix: 'src:', off: 'off', keyOf: function (x) { return x.key; },
|
|
1963
1978
|
move: function (k, d) { return Stremio.moveFeedSource(k, d); }, toggle: function (k) { Stremio.toggleFeedSource(k); },
|
|
1964
1979
|
hidden: function (k) { return Stremio.isFeedSourceOff(k); } };
|
|
1965
|
-
// â–²
|
|
1980
|
+
// ⇈ ▲ ▼ ⇊ for a row of a list you put in order: to the top, one place up, one down, to the
|
|
1981
|
+
// bottom. move(dir, ctl) moves it (dir -Infinity / -1 / 1 / Infinity: see U.moveIn); mark(node,
|
|
1982
|
+
// ctl) tags each button, so the page can focus the one used once it's drawn again.
|
|
1983
|
+
var ORDER_BTNS = [['top', '⇈', -Infinity], ['up', '▲', -1], ['down', '▼', 1], ['bottom', '⇊', Infinity]];
|
|
1984
|
+
function orderButtons(move, mark) {
|
|
1985
|
+
return ORDER_BTNS.map(function (b) {
|
|
1986
|
+
var n = U.el('div', 'feed-btn focusable', b[1]);
|
|
1987
|
+
if (mark) { mark(n, b[0]); }
|
|
1988
|
+
n.__onselect = function () { move(b[2], b[0]); };
|
|
1989
|
+
return n;
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
function ctlIndex(ctl) { for (var i = 0; i < ORDER_BTNS.length; i++) { if (ORDER_BTNS[i][0] === ctl) { return i; } } return 1; }
|
|
1993
|
+
// ⇈ ▲ ▼ ⇊ / name rows for an ordered list (L: keyOf, hidden, move, toggle, off, prefix).
|
|
1966
1994
|
// After a change the page is drawn again, focused on the control used (refeed).
|
|
1967
1995
|
function feedRows(wrap, kind, items, L) {
|
|
1968
1996
|
items.forEach(function (item) {
|
|
@@ -1970,20 +1998,14 @@
|
|
|
1970
1998
|
var hidden = L.hidden(key);
|
|
1971
1999
|
var frow = U.el('div', 'feed-row' + (hidden ? ' feed-off' : ''));
|
|
1972
2000
|
|
|
1973
|
-
var
|
|
1974
|
-
|
|
1975
|
-
up.__onselect = function () { L.move(key, -1).then(function () { refeed(kind, fk, 'up'); }); };
|
|
1976
|
-
|
|
1977
|
-
var down = U.el('div', 'feed-btn focusable', 'â–¼');
|
|
1978
|
-
down.setAttribute('data-fk', fk); down.setAttribute('data-ctl', 'down');
|
|
1979
|
-
down.__onselect = function () { L.move(key, 1).then(function () { refeed(kind, fk, 'down'); }); };
|
|
2001
|
+
var btns = orderButtons(function (dir, ctl) { L.move(key, dir).then(function () { refeed(kind, fk, ctl); }); },
|
|
2002
|
+
function (n, ctl) { n.setAttribute('data-fk', fk); n.setAttribute('data-ctl', ctl); });
|
|
1980
2003
|
|
|
1981
2004
|
var name = U.el('div', 'feed-name focusable', item.name + (hidden ? ' (' + (L.off || 'hidden') + ')' : ''));
|
|
1982
2005
|
name.setAttribute('data-fk', fk); name.setAttribute('data-ctl', 'name');
|
|
1983
2006
|
name.__onselect = function () { L.toggle(key); refeed(kind, fk, 'name'); };
|
|
1984
2007
|
|
|
1985
|
-
frow.appendChild(
|
|
1986
|
-
frow.appendChild(down);
|
|
2008
|
+
btns.forEach(function (b) { frow.appendChild(b); });
|
|
1987
2009
|
frow.appendChild(name);
|
|
1988
2010
|
wrap.appendChild(frow);
|
|
1989
2011
|
});
|
package/js/youtube.js
CHANGED
|
@@ -130,9 +130,7 @@ var YouTube = (function () {
|
|
|
130
130
|
function move(id, dir) {
|
|
131
131
|
var c = cfg(), i = -1;
|
|
132
132
|
c.catalogs.forEach(function (x, n) { if (x.id === id) { i = n; } });
|
|
133
|
-
|
|
134
|
-
if (i < 0 || j < 0 || j >= c.catalogs.length) { return false; }
|
|
135
|
-
var t = c.catalogs[i]; c.catalogs[i] = c.catalogs[j]; c.catalogs[j] = t;
|
|
133
|
+
if (!U.moveIn(c.catalogs, i, dir)) { return false; } // (one place, or to the top / bottom)
|
|
136
134
|
save(c);
|
|
137
135
|
return true;
|
|
138
136
|
}
|
package/js/ytplay.js
CHANGED
|
@@ -71,6 +71,15 @@ var YTPlay = (function () {
|
|
|
71
71
|
return U.getJSON(PROXY + q + a.q, force ? 5000 : 30000, 65536, null, a.h);
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
+
// An HLS stream's quality list, read by the service with the stream's own User-Agent /
|
|
75
|
+
// Referer (h: { ua, ref }), which a web page can't send: its text.
|
|
76
|
+
function hlsMaster(url, h) {
|
|
77
|
+
if (MODE === 'browser') { return Promise.reject(new Error('no service')); }
|
|
78
|
+
return ensureService().then(entryAuth).then(function (a) {
|
|
79
|
+
var q = '/live/master?u=' + encodeURIComponent(url) + (h && h.ua ? '&ua=' + encodeURIComponent(h.ua) : '') + (h && h.ref ? '&ref=' + encodeURIComponent(h.ref) : '');
|
|
80
|
+
return U.getText(PROXY + q + a.q, 12000, 524288, null, false, a.h);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
74
83
|
// An AIOManager account's addons (its Hydra API, through the service: an instance only
|
|
75
84
|
// answers the web origins it's set to): { addons: [{ url, name, enabled }] }. base: its
|
|
76
85
|
// address; key: the account's API key (a header, never in the link).
|
|
@@ -172,6 +181,6 @@ var YTPlay = (function () {
|
|
|
172
181
|
return MODE === 'tizenbrew' ? 'TizenBrew module' : MODE === 'wgt' ? 'Sideloaded app (.wgt)' : 'Browser (development)';
|
|
173
182
|
}
|
|
174
183
|
|
|
175
|
-
return { ensureService: ensureService, resolverUrl: resolverUrl, infoUrl: infoUrl, canPlay: canPlay, liveOpen: liveOpen, aiomAddons: aiomAddons, entryOpen: entryOpen, entryPoll: entryPoll, entryClose: entryClose, subsUrl: subsUrl, audioLang: audioLang, setAudioLang: setAudioLang, installMode: function () { return MODE; },
|
|
184
|
+
return { ensureService: ensureService, resolverUrl: resolverUrl, infoUrl: infoUrl, canPlay: canPlay, liveOpen: liveOpen, hlsMaster: hlsMaster, aiomAddons: aiomAddons, entryOpen: entryOpen, entryPoll: entryPoll, entryClose: entryClose, subsUrl: subsUrl, audioLang: audioLang, setAudioLang: setAudioLang, installMode: function () { return MODE; },
|
|
176
185
|
installLabel: installLabel, unavailableMessage: unavailableMessage, isRunning: isRunning, port: PORT };
|
|
177
186
|
})();
|