stremiok 2.1.0 → 2.2.1
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 +97 -0
- package/README.md +10 -9
- package/config.xml +1 -1
- package/css/style.css +27 -0
- package/index.html +34 -31
- package/js/app.js +25 -10
- package/js/epg.js +320 -0
- package/js/iptv.js +198 -25
- package/js/keys.js +2 -0
- package/js/player-controls.js +50 -8
- package/js/player-core.js +112 -12
- package/js/player-lang.js +66 -5
- package/js/player-store.js +23 -2
- package/js/player-subs.js +57 -13
- package/js/stremio.js +70 -6
- package/js/trakt.js +1 -0
- package/js/util.js +24 -13
- package/js/view-core.js +128 -55
- package/js/view-detail.js +1 -1
- package/js/view-guide.js +371 -0
- package/js/view-home.js +148 -33
- package/js/view-settings.js +241 -90
- package/js/views.js +4 -3
- package/js/ytplay.js +53 -5
- package/package.json +2 -1
- package/service/index.js +863 -46
package/js/view-settings.js
CHANGED
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
function markHomeDirty() { return V.markHomeDirty.apply(null, arguments); } // home
|
|
18
18
|
function onModalClose() { return V.onModalClose.apply(null, arguments); } // core
|
|
19
19
|
function pickList() { return V.pickList.apply(null, arguments); } // core
|
|
20
|
-
function toggleList() { return V.toggleList.apply(null, arguments); } // core
|
|
21
20
|
function playlistActions() { return V.playlistActions.apply(null, arguments); } // core
|
|
22
21
|
function scr() { return V.scr.apply(null, arguments); } // core
|
|
23
22
|
function showModal() { return V.showModal.apply(null, arguments); } // core
|
|
@@ -99,11 +98,35 @@
|
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
// ================= SUBTITLE CONFIG =================
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
['
|
|
101
|
+
// Every language player-lang recognizes on a track (its LANG_ALIASES), by name.
|
|
102
|
+
var SUB_LANGS = [['off', 'Off'], ['afr', 'Afrikaans'], ['alb', 'Albanian'], ['ara', 'Arabic'],
|
|
103
|
+
['arg', 'Aragonese'], ['arm', 'Armenian'], ['ast', 'Asturian'], ['aze', 'Azerbaijani'],
|
|
104
|
+
['baq', 'Basque'], ['bel', 'Belarusian'], ['ben', 'Bengali'], ['bos', 'Bosnian'],
|
|
105
|
+
['bre', 'Breton'], ['bul', 'Bulgarian'], ['bur', 'Burmese'], ['cat', 'Catalan'],
|
|
106
|
+
['chi', 'Chinese'], ['hrv', 'Croatian'], ['cze', 'Czech'], ['dan', 'Danish'], ['prs', 'Dari'],
|
|
107
|
+
['dut', 'Dutch'], ['eng', 'English'], ['epo', 'Esperanto'], ['est', 'Estonian'],
|
|
108
|
+
['ext', 'Extremaduran'], ['fil', 'Filipino'], ['fin', 'Finnish'], ['fre', 'French'],
|
|
109
|
+
['glg', 'Galician'], ['geo', 'Georgian'], ['ger', 'German'], ['gre', 'Greek'],
|
|
110
|
+
['heb', 'Hebrew'], ['hin', 'Hindi'], ['hun', 'Hungarian'], ['ice', 'Icelandic'],
|
|
111
|
+
['ibo', 'Igbo'], ['ind', 'Indonesian'], ['ina', 'Interlingua'], ['gle', 'Irish'],
|
|
112
|
+
['ita', 'Italian'], ['jpn', 'Japanese'], ['kan', 'Kannada'], ['kaz', 'Kazakh'],
|
|
113
|
+
['khm', 'Khmer'], ['kor', 'Korean'], ['kur', 'Kurdish'], ['lav', 'Latvian'],
|
|
114
|
+
['lit', 'Lithuanian'], ['ltz', 'Luxembourgish'], ['mac', 'Macedonian'], ['may', 'Malay'],
|
|
115
|
+
['mal', 'Malayalam'], ['mni', 'Manipuri'], ['mar', 'Marathi'], ['mon', 'Mongolian'],
|
|
116
|
+
['mne', 'Montenegrin'], ['nav', 'Navajo'], ['nep', 'Nepali'], ['sme', 'Northern Sami'],
|
|
117
|
+
['nor', 'Norwegian'], ['oci', 'Occitan'], ['ori', 'Odia'], ['pus', 'Pashto'],
|
|
118
|
+
['per', 'Persian'], ['pol', 'Polish'], ['por', 'Portuguese'], ['pan', 'Punjabi'],
|
|
119
|
+
['rum', 'Romanian'], ['rus', 'Russian'], ['sat', 'Santali'], ['gla', 'Scottish Gaelic'],
|
|
120
|
+
['srp', 'Serbian'], ['snd', 'Sindhi'], ['sin', 'Sinhala'], ['slo', 'Slovak'],
|
|
121
|
+
['slv', 'Slovenian'], ['som', 'Somali'], ['spa', 'Spanish'], ['swa', 'Swahili'],
|
|
122
|
+
['swe', 'Swedish'], ['syr', 'Syriac'], ['tam', 'Tamil'], ['tat', 'Tatar'], ['tel', 'Telugu'],
|
|
123
|
+
['tha', 'Thai'], ['tok', 'Toki Pona'], ['tur', 'Turkish'], ['tuk', 'Turkmen'],
|
|
124
|
+
['ukr', 'Ukrainian'], ['urd', 'Urdu'], ['uzb', 'Uzbek'], ['vie', 'Vietnamese'],
|
|
125
|
+
['wel', 'Welsh']];
|
|
105
126
|
// A second choice, turned on when nothing is in the default language (from any source).
|
|
106
127
|
var SUB_LANGS2 = [['none', 'None']].concat(SUB_LANGS.slice(1));
|
|
128
|
+
// The subtitle menu with only the three languages above (and tracks of no known language).
|
|
129
|
+
var SUB_ONLY = [['off', 'Off'], ['on', 'On']];
|
|
107
130
|
var SUB_SIZES = [['small', 'Small'], ['medium', 'Medium'], ['large', 'Large'], ['xlarge', 'Extra Large']];
|
|
108
131
|
|
|
109
132
|
function labelFor(list, v) {
|
|
@@ -207,14 +230,16 @@
|
|
|
207
230
|
get: function () { return { spoilers: Stremio.getPrefs().spoilers }; },
|
|
208
231
|
set: function (cfg) { Stremio.setPref('spoilers', cfg.spoilers); }
|
|
209
232
|
}));
|
|
210
|
-
|
|
233
|
+
var menu = sectionRow('Menu ›', 'Order the top menu, and show or hide its tabs', 'menuorder');
|
|
234
|
+
menu.className += ' cfg-row';
|
|
235
|
+
wrap.appendChild(menu);
|
|
211
236
|
var feeds = sectionRow('Home feeds ›', 'Order the Home rows, and show or hide each one', 'feeds');
|
|
212
237
|
feeds.className += ' cfg-row';
|
|
213
238
|
wrap.appendChild(feeds);
|
|
214
239
|
var sfeeds = sectionRow('Search results ›', 'Order the Search rows (one per catalog that can search), and show or hide each one', 'searchfeeds');
|
|
215
240
|
sfeeds.className += ' cfg-row';
|
|
216
241
|
wrap.appendChild(sfeeds);
|
|
217
|
-
var back = _focusChild === 'feeds' ? feeds : _focusChild === 'searchfeeds' ? sfeeds : null;
|
|
242
|
+
var back = _focusChild === 'menuorder' ? menu : _focusChild === 'feeds' ? feeds : _focusChild === 'searchfeeds' ? sfeeds : null;
|
|
218
243
|
_focusChild = null;
|
|
219
244
|
if (back) { Focus.setScope(scr('settings'), back); } else { focusSettings(); }
|
|
220
245
|
}
|
|
@@ -337,7 +362,7 @@
|
|
|
337
362
|
// BACK goes up one level, onto the row it came from: from a section to its group (on
|
|
338
363
|
// that section, not the first one), from Home feeds / Search results to Appearance,
|
|
339
364
|
// from a group to the Settings list (on that group).
|
|
340
|
-
var SECTION_PARENT = { feeds: 'appearance', searchfeeds: 'appearance', aiomanager: 'addons' };
|
|
365
|
+
var SECTION_PARENT = { menuorder: 'appearance', feeds: 'appearance', searchfeeds: 'appearance', aiomanager: 'addons', subtitlelook: 'subtitles', rankfilters: 'ranking', rankplay: 'ranking' };
|
|
341
366
|
Object.keys(SETTINGS_GROUPS).forEach(function (g) {
|
|
342
367
|
SETTINGS_GROUPS[g].rows.forEach(function (row) { SECTION_PARENT[row[2]] = g; });
|
|
343
368
|
});
|
|
@@ -364,11 +389,15 @@
|
|
|
364
389
|
else if (_settingsSection === 'integrations') { settingsIntegrations(wrap); }
|
|
365
390
|
else if (_settingsSection === 'audio') { settingsAudio(wrap); }
|
|
366
391
|
else if (_settingsSection === 'subtitles') { settingsSubtitles(wrap); }
|
|
392
|
+
else if (_settingsSection === 'subtitlelook') { settingsSubtitleLook(wrap); }
|
|
367
393
|
else if (_settingsSection === 'skipping') { settingsSkipping(wrap); }
|
|
368
394
|
else if (_settingsSection === 'seeking') { settingsSeeking(wrap); }
|
|
395
|
+
else if (_settingsSection === 'menuorder') { settingsFeeds(wrap, 'menu'); }
|
|
369
396
|
else if (_settingsSection === 'feeds') { settingsFeeds(wrap, 'home'); }
|
|
370
397
|
else if (_settingsSection === 'searchfeeds') { settingsFeeds(wrap, 'search'); }
|
|
371
398
|
else if (_settingsSection === 'ranking') { settingsFeeds(wrap, 'ranking'); }
|
|
399
|
+
else if (_settingsSection === 'rankfilters') { settingsRankFilters(wrap); }
|
|
400
|
+
else if (_settingsSection === 'rankplay') { settingsRankPlay(wrap); }
|
|
372
401
|
else if (SETTINGS_GROUPS[_settingsSection]) { settingsGroup(wrap, _settingsSection); }
|
|
373
402
|
else if (_settingsSection === 'logs') { settingsLogs(wrap); }
|
|
374
403
|
else if (_settingsSection === 'storage') { settingsStorage(wrap); }
|
|
@@ -867,6 +896,15 @@
|
|
|
867
896
|
var _iptvFocus = null; // { id, ctl } to focus once the page is drawn again
|
|
868
897
|
function two(n) { return (n < 10 ? '0' : '') + n; }
|
|
869
898
|
function thousands(n) { return ('' + n).replace(/\B(?=(\d{3})+(?!\d))/g, ','); }
|
|
899
|
+
// The playlist rows IPTV Settings drew last: their guide lines kept current by one listener
|
|
900
|
+
// (one added each time it was drawn used to pile up, each holding its rows).
|
|
901
|
+
var _guideRows = [];
|
|
902
|
+
if (typeof EPG !== 'undefined' && EPG.onChange) {
|
|
903
|
+
EPG.onChange(function () {
|
|
904
|
+
_guideRows = _guideRows.filter(function (r) { return document.documentElement.contains(r.info); });
|
|
905
|
+
_guideRows.forEach(function (r) { r.info.textContent = playlistInfo(r.p); });
|
|
906
|
+
});
|
|
907
|
+
}
|
|
870
908
|
function playlistInfo(p) {
|
|
871
909
|
if (p.off) { return 'Off — its channels aren’t shown'; }
|
|
872
910
|
var st = Stremio.iptvStatus(p.id), parts = [];
|
|
@@ -875,13 +913,65 @@
|
|
|
875
913
|
if (st.fetchedAt) { var d = new Date(st.fetchedAt); parts.push('downloaded ' + U.dayLabel(st.fetchedAt) + ' ' + two(d.getHours()) + ':' + two(d.getMinutes())); }
|
|
876
914
|
if (st.dropped) { parts.push(thousands(st.dropped) + ' more left out (a playlist keeps its first 30,000)'); }
|
|
877
915
|
if (st.uncached) { parts.push('too large to save on the TV: downloaded each launch'); }
|
|
916
|
+
var g = guideInfo(p);
|
|
917
|
+
if (g) { parts.push(g); }
|
|
878
918
|
return parts.join(' · ');
|
|
879
919
|
}
|
|
920
|
+
// Its TV guide (js/epg.js): being read, read (for how many of its channels), or why not.
|
|
921
|
+
function guideInfo(p) {
|
|
922
|
+
var g = typeof EPG !== 'undefined' && EPG.status ? EPG.status(p.id) : null;
|
|
923
|
+
if (!g) { return typeof EPG !== 'undefined' && EPG.available && EPG.available() ? 'no TV guide' : ''; }
|
|
924
|
+
if (g.state === 'ready') {
|
|
925
|
+
var d = new Date(g.at);
|
|
926
|
+
return 'TV guide: ' + thousands(g.channels) + ' channel' + (g.channels === 1 ? '' : 's') + ', ' + thousands(g.programmes) + ' programs' + (g.partial ? ' (part of it: too large)' : '') +
|
|
927
|
+
(g.at ? ', read ' + U.dayLabel(g.at) + ' ' + two(d.getHours()) + ':' + two(d.getMinutes()) : '') + (g.error ? ' (the last read failed: ' + g.error + ')' : '');
|
|
928
|
+
}
|
|
929
|
+
if (g.state === 'failed') { return '⚠ TV guide: ' + (g.error || 'couldn’t be read'); }
|
|
930
|
+
return 'TV guide: reading…';
|
|
931
|
+
}
|
|
880
932
|
function playlistProblem(p) {
|
|
881
933
|
var st = Stremio.iptvStatus(p.id);
|
|
882
934
|
if (p.off || !st.error) { return ''; }
|
|
883
935
|
return '⚠ ' + (st.stale ? 'Couldn’t refresh it (showing the saved copy): ' : '') + st.error;
|
|
884
936
|
}
|
|
937
|
+
// + Add Xtream account: its server, username and password, a page each (a whole Xtream link
|
|
938
|
+
// in the server's fills the rest); checked with the server, then added (Stremio.iptvAddXtream).
|
|
939
|
+
function addXtream() {
|
|
940
|
+
function done(base, user, pass) {
|
|
941
|
+
U.toast('Checking the account…', 15000);
|
|
942
|
+
IPTV.xtreamCheck(base, user, pass).then(function (r) {
|
|
943
|
+
if (r.unreachable) { U.toast('Couldn’t reach the server: check its address (nothing was saved)', 5000); return; }
|
|
944
|
+
if (r.refused) { U.toast('The server refused that username and password' + (r.status ? ' (' + r.status + ')' : '') + ': nothing was saved', 5000); return; }
|
|
945
|
+
Stremio.iptvAddXtream(base, user, pass);
|
|
946
|
+
refreshAddonRows('local:iptv');
|
|
947
|
+
U.toast('Xtream account added: reading its channels and TV guide…', 4000);
|
|
948
|
+
renderSettings();
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
App.openEntry({
|
|
952
|
+
title: 'Xtream account: server', phone: 'playlist',
|
|
953
|
+
hint: 'The server address your provider gave you (http://line.example.com:8080), or a whole Xtream link. RETURN cancels.',
|
|
954
|
+
value: 'http://', placeholder: 'http://line.example.com:8080',
|
|
955
|
+
validate: function (v) { return IPTV.xtreamParts(v) ? null : 'That isn’t a server address'; },
|
|
956
|
+
onConfirm: function (v) {
|
|
957
|
+
var x = IPTV.xtreamParts(v);
|
|
958
|
+
App.back();
|
|
959
|
+
if (x.user && x.pass) { done(x.base, x.user, x.pass); return; }
|
|
960
|
+
App.openEntry({
|
|
961
|
+
title: 'Xtream account: username', hint: 'Your account’s username. RETURN cancels.', value: x.user, placeholder: 'username',
|
|
962
|
+
validate: function (u) { return ('' + u).trim() ? null : 'Type the username'; },
|
|
963
|
+
onConfirm: function (u) {
|
|
964
|
+
App.back();
|
|
965
|
+
App.openEntry({
|
|
966
|
+
title: 'Xtream account: password', hint: 'Your account’s password (kept on the TV, in the playlist’s link). RETURN cancels.', value: '', placeholder: 'password', secret: true,
|
|
967
|
+
validate: function (p) { return ('' + p) ? null : 'Type the password'; },
|
|
968
|
+
onConfirm: function (p) { App.back(); done(x.base, ('' + u).trim(), '' + p); }
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
}
|
|
885
975
|
function settingsIptv(wrap) {
|
|
886
976
|
wrap.appendChild(U.el('h2', null, 'IPTV'));
|
|
887
977
|
var playlists = Stremio.iptvGetPlaylists ? Stremio.iptvGetPlaylists() : [];
|
|
@@ -925,6 +1015,7 @@
|
|
|
925
1015
|
rows.push({ p: playlist, info: info, problem: problem, idx: idx });
|
|
926
1016
|
});
|
|
927
1017
|
|
|
1018
|
+
_guideRows = rows; // (their guide lines kept current as guides are read: _guideRows)
|
|
928
1019
|
if (statusEl) {
|
|
929
1020
|
Stremio.iptvLoad().then(function () {
|
|
930
1021
|
var channels = 0, groups = 0, failed = 0, on = 0;
|
|
@@ -962,6 +1053,11 @@
|
|
|
962
1053
|
});
|
|
963
1054
|
};
|
|
964
1055
|
wrap.appendChild(add);
|
|
1056
|
+
var xt = U.el('div', 'addon-item focusable add-row');
|
|
1057
|
+
xt.appendChild(U.el('div', 'a-name', '+ Add Xtream account'));
|
|
1058
|
+
xt.appendChild(U.el('div', 'a-url', 'Its server, username and password: its channels and TV guide added'));
|
|
1059
|
+
xt.__onselect = addXtream;
|
|
1060
|
+
wrap.appendChild(xt);
|
|
965
1061
|
|
|
966
1062
|
wrap.appendChild(U.el('div', 'hint',
|
|
967
1063
|
'Channels are grouped into IPTV and Radio rows on Home. ▲ / ▼ set the playlists\' order: a channel in two playlists comes from the first.'));
|
|
@@ -1330,14 +1426,7 @@
|
|
|
1330
1426
|
wrap.appendChild(subPickRow('Default language', SUB_LANGS, 'lang'));
|
|
1331
1427
|
wrap.appendChild(subPickRow('Second choice', SUB_LANGS2, 'lang2'));
|
|
1332
1428
|
wrap.appendChild(subPickRow('Second subtitle (at the top)', SUB_LANGS, 'second'));
|
|
1333
|
-
wrap.appendChild(subPickRow('
|
|
1334
|
-
// [id, label] for the picker (colors with a swatch)
|
|
1335
|
-
function plain(list) { return list.map(function (x) { return [x[0], x[1]]; }); }
|
|
1336
|
-
wrap.appendChild(subPickRow('Color', Player.SUB_COLORS.map(function (x) { return [x[0], x[1], null, x[2]]; }), 'color'));
|
|
1337
|
-
wrap.appendChild(subPickRow('Background', plain(Player.SUB_BGS), 'bg'));
|
|
1338
|
-
wrap.appendChild(subPickRow('Edge', plain(Player.SUB_EDGES), 'edge'));
|
|
1339
|
-
wrap.appendChild(subPickRow('Position', plain(Player.SUB_POSITIONS), 'pos'));
|
|
1340
|
-
wrap.appendChild(subPickRow('Font', Player.SUB_FONTS, 'font'));
|
|
1429
|
+
wrap.appendChild(subPickRow('Only my languages', SUB_ONLY, 'only'));
|
|
1341
1430
|
// lines to leave out: those with one of your words (a subtitle site's ads, a file's name…)
|
|
1342
1431
|
var skip = Player.getSubCfg().skip || [];
|
|
1343
1432
|
var sk = U.el('div', 'addon-item focusable cfg-row');
|
|
@@ -1350,6 +1439,21 @@
|
|
|
1350
1439
|
onConfirm: function (v) { var c = Player.getSubCfg(); c.skip = ('' + v).split(','); Player.setSubCfg(c); App.back(); renderSettings(); } });
|
|
1351
1440
|
};
|
|
1352
1441
|
wrap.appendChild(sk);
|
|
1442
|
+
var look = sectionRow('Appearance ›', 'Size, color, background, edge, position, font', 'subtitlelook'); // (their own page: settingsSubtitleLook)
|
|
1443
|
+
wrap.appendChild(look);
|
|
1444
|
+
if (_focusChild === 'subtitlelook') { _focusChild = null; Focus.setScope(scr('settings'), look); } else { focusSettings(); }
|
|
1445
|
+
}
|
|
1446
|
+
// Subtitles › Appearance: how subtitles look.
|
|
1447
|
+
function settingsSubtitleLook(wrap) {
|
|
1448
|
+
wrap.appendChild(U.el('h2', null, 'Subtitle appearance'));
|
|
1449
|
+
wrap.appendChild(subPickRow('Size', SUB_SIZES, 'size'));
|
|
1450
|
+
// [id, label] for the picker (colors with a swatch)
|
|
1451
|
+
function plain(list) { return list.map(function (x) { return [x[0], x[1]]; }); }
|
|
1452
|
+
wrap.appendChild(subPickRow('Color', Player.SUB_COLORS.map(function (x) { return [x[0], x[1], null, x[2]]; }), 'color'));
|
|
1453
|
+
wrap.appendChild(subPickRow('Background', plain(Player.SUB_BGS), 'bg'));
|
|
1454
|
+
wrap.appendChild(subPickRow('Edge', plain(Player.SUB_EDGES), 'edge'));
|
|
1455
|
+
wrap.appendChild(subPickRow('Position', plain(Player.SUB_POSITIONS), 'pos'));
|
|
1456
|
+
wrap.appendChild(subPickRow('Font', Player.SUB_FONTS, 'font'));
|
|
1353
1457
|
focusSettings();
|
|
1354
1458
|
}
|
|
1355
1459
|
|
|
@@ -1386,34 +1490,6 @@
|
|
|
1386
1490
|
focusSettings();
|
|
1387
1491
|
}
|
|
1388
1492
|
|
|
1389
|
-
// Home tabs: hide the ones you never use (Watching always stays: it's where Home opens). A
|
|
1390
|
-
// row like the ones above it; OK opens the tabs, OK on one shows or hides it (the list
|
|
1391
|
-
// stays open). A hidden tab's rows are back when it's shown again.
|
|
1392
|
-
function homeTabsRow() {
|
|
1393
|
-
function hiddenNow() { return Stremio.getPrefs().hiddenTabs || []; }
|
|
1394
|
-
var r = U.el('div', 'addon-item focusable cfg-row'), name = U.el('div', 'a-name');
|
|
1395
|
-
var tabs = (V.HOME_TABS || []).filter(function (t) { return t.key !== 'continue'; });
|
|
1396
|
-
function draw() {
|
|
1397
|
-
var hidden = hiddenNow().filter(function (k) { return tabs.some(function (t) { return t.key === k; }); });
|
|
1398
|
-
name.textContent = 'Home tabs: ' + (hidden.length ? hidden.length + ' hidden' : 'All shown');
|
|
1399
|
-
}
|
|
1400
|
-
draw();
|
|
1401
|
-
r.appendChild(name);
|
|
1402
|
-
r.appendChild(U.el('div', 'a-url', 'Press OK to choose'));
|
|
1403
|
-
r.__onselect = function () {
|
|
1404
|
-
var hidden = hiddenNow();
|
|
1405
|
-
toggleList('Home tabs', tabs.map(function (t) { return [t.key, t.label + (t.key === 'local' ? ' (with a USB drive in)' : ''), hidden.indexOf(t.key) === -1]; }),
|
|
1406
|
-
{ on: 'Shown', off: 'Hidden' }, function (key, on) {
|
|
1407
|
-
var now = hiddenNow().filter(function (k) { return k !== key; });
|
|
1408
|
-
if (!on) { now.push(key); }
|
|
1409
|
-
Stremio.setPref('hiddenTabs', now);
|
|
1410
|
-
markHomeDirty();
|
|
1411
|
-
draw();
|
|
1412
|
-
});
|
|
1413
|
-
};
|
|
1414
|
-
return r;
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
1493
|
var POSTER_SHAPES = [['portrait', 'Portrait'], ['landscape', 'Landscape']];
|
|
1418
1494
|
// ----- Diagnostics › Logs: the recent problems U.log kept (in memory, URLs masked), and
|
|
1419
1495
|
// how long launches took -----
|
|
@@ -1630,14 +1706,17 @@
|
|
|
1630
1706
|
if (!inf.entries.length) { line(dbOn ? 'Nothing in the TV’s database.' : 'The TV’s database isn’t used (turned off above).'); }
|
|
1631
1707
|
line('Settings storage: ' + fmtBytes(inf.ls) + ' (progress, watched marks, settings…)' + (inf.lsTop.length ? '; the largest:' : ''));
|
|
1632
1708
|
inf.lsTop.forEach(function (e) { line(' ' + fmtBytes(e.bytes) + ' ' + e.key); });
|
|
1709
|
+
if (inf.guides && inf.guides.count) { line('Saved TV guides (in StremioK’s folder): ' + fmtBytes(inf.guides.bytes) + ' in ' + inf.guides.count + (inf.guides.count === 1 ? ' file' : ' files')); }
|
|
1633
1710
|
if (inf.disk) { line('The TV’s own disk: ' + fmtBytes(inf.disk.free) + ' free of ' + fmtBytes(inf.disk.total) + ' (what the TV allows StremioK follows it: freeing space, unused apps for one, raises it)'); }
|
|
1634
1711
|
}
|
|
1635
1712
|
r.__onselect = function () {
|
|
1636
1713
|
if (!U.bigStore || !U.bigStore.info || _storageInfo === 'busy') { return; }
|
|
1637
1714
|
_storageInfo = 'busy'; renderSettings();
|
|
1638
|
-
|
|
1715
|
+
var guides = (typeof YTPlay !== 'undefined' && YTPlay.epgFiles) ? YTPlay.epgFiles().then(null, function () { return null; }) : null;
|
|
1716
|
+
Promise.all([U.bigStore.info(), U.diskInfo ? U.diskInfo() : null, guides]).then(function (both) {
|
|
1639
1717
|
var inf = both[0], ls = 0, lsAll = [];
|
|
1640
1718
|
inf.disk = both[1];
|
|
1719
|
+
inf.guides = both[2]; // ({ count, bytes }: the TV guides StremioK's service saved)
|
|
1641
1720
|
inf.lsKeys = {};
|
|
1642
1721
|
try {
|
|
1643
1722
|
for (var i = 0; i < window.localStorage.length; i++) {
|
|
@@ -1669,12 +1748,15 @@
|
|
|
1669
1748
|
}
|
|
1670
1749
|
var clr = U.el('div', 'addon-item focusable');
|
|
1671
1750
|
clr.appendChild(U.el('div', 'a-name', 'Clear saved copies'));
|
|
1672
|
-
clr.appendChild(U.el('div', 'a-url', 'Home’s saved rows, IPTV playlist copies, the USB index: made again when needed (playlists download again)'));
|
|
1751
|
+
clr.appendChild(U.el('div', 'a-url', 'Home’s saved rows, IPTV playlist copies, saved TV guides, the USB index: made again when needed (playlists and guides download again)'));
|
|
1673
1752
|
clr.__onselect = function () {
|
|
1674
|
-
confirm('Clear the copies StremioK can make again (Home rows, playlist copies, USB index)?', 'Clear', function () {
|
|
1753
|
+
confirm('Clear the copies StremioK can make again (Home rows, playlist copies, TV guides, USB index)?', 'Clear', function () {
|
|
1675
1754
|
U.bigStore.keys().then(function (ks) {
|
|
1676
1755
|
return Promise.all(ks.filter(function (k) { return /^stremiok\.(homecache|local\.index|iptv\.cache\.)/.test(k); }).map(function (k) { return U.bigStore.remove(k); }));
|
|
1677
1756
|
}).then(function () {
|
|
1757
|
+
return (typeof YTPlay !== 'undefined' && YTPlay.epgClear) ? YTPlay.epgClear().then(null, function () {}) : null; // (the saved guides: read again as they're needed)
|
|
1758
|
+
}).then(function () {
|
|
1759
|
+
if (typeof EPG !== 'undefined' && EPG.reset) { EPG.reset(); }
|
|
1678
1760
|
if (typeof LocalMedia !== 'undefined') { LocalMedia.clearIndex(); }
|
|
1679
1761
|
_storageInfo = null;
|
|
1680
1762
|
U.toast('Saved copies cleared');
|
|
@@ -1846,7 +1928,28 @@
|
|
|
1846
1928
|
// ----- Home feeds and Search results (Settings > Appearance): order, show / hide -----
|
|
1847
1929
|
// kind 'home': the Home rows (every catalog); 'search': the Search rows (every catalog
|
|
1848
1930
|
// that can search, IPTV as one per type). ▲/▼ moves one; OK on its name shows / hides it.
|
|
1931
|
+
// 'menu': the top menu between Watching and Settings (the tabs, Discover, Search: ViewParts'
|
|
1932
|
+
// menuOrder); OK on a tab shows / hides it, Discover and Search always show.
|
|
1933
|
+
var MENU_FIXED = { discover: 'Discover', search: 'Search' };
|
|
1934
|
+
function menuHidden(k) { return !MENU_FIXED[k] && (Stremio.getPrefs().hiddenTabs || []).indexOf(k) !== -1; }
|
|
1849
1935
|
var FEED_KINDS = {
|
|
1936
|
+
menu: { section: 'menuorder', title: 'Appearance › Menu', hint: 'Watching comes first and Settings last: ⇈ ▲ ▼ ⇊ put the rest in your order · OK on a tab shows/hides it.',
|
|
1937
|
+
list: function () {
|
|
1938
|
+
return Promise.resolve(V.menuOrder().map(function (k) {
|
|
1939
|
+
var t = (V.HOME_TABS || []).filter(function (x) { return x.key === k; })[0];
|
|
1940
|
+
return { key: k, name: MENU_FIXED[k] || (t ? t.label : k) + (k === 'local' ? ' (with a USB drive in)' : k === 'guide' ? ' (once a TV guide is read)' : '') };
|
|
1941
|
+
}));
|
|
1942
|
+
},
|
|
1943
|
+
keyOf: function (x) { return x.key; },
|
|
1944
|
+
move: function (k, d) { V.moveMenuItem(k, d); markHomeDirty(); return Promise.resolve(); },
|
|
1945
|
+
toggle: function (k) {
|
|
1946
|
+
if (MENU_FIXED[k]) { U.toast(MENU_FIXED[k] + ' is always in the menu'); return; }
|
|
1947
|
+
var now = (Stremio.getPrefs().hiddenTabs || []).filter(function (x) { return x !== k; });
|
|
1948
|
+
if (!menuHidden(k)) { now.push(k); }
|
|
1949
|
+
Stremio.setPref('hiddenTabs', now);
|
|
1950
|
+
markHomeDirty();
|
|
1951
|
+
},
|
|
1952
|
+
hidden: menuHidden },
|
|
1850
1953
|
home: { section: 'feeds', title: 'Appearance › Home feeds', hint: '▲/▼ reorder a feed · OK on the name shows/hides it on Home.',
|
|
1851
1954
|
list: function () { return Stremio.listCatalogsOrdered(); }, keyOf: function (c) { return Stremio.catalogKey(c); },
|
|
1852
1955
|
move: function (k, d) { return Stremio.moveFeed(k, d); }, toggle: function (k) { Stremio.toggleFeed(k); },
|
|
@@ -1860,7 +1963,7 @@
|
|
|
1860
1963
|
list: function () { return Promise.resolve(Stremio.getRanking().criteria); }, keyOf: function (c) { return c.key; },
|
|
1861
1964
|
move: function (k, d) { Stremio.moveRanking(k, d); return Promise.resolve(); }, toggle: function (k) { Stremio.toggleRanking(k); },
|
|
1862
1965
|
hidden: function (k) { return !Stremio.getRanking().criteria.some(function (c) { return c.key === k && c.on; }); },
|
|
1863
|
-
before: function (wrap) { // played before, above the resolution groups
|
|
1966
|
+
before: function (wrap) { // played before, above the resolution groups; then the two pages
|
|
1864
1967
|
var played = Stremio.getRanking().played;
|
|
1865
1968
|
var r = U.el('div', 'addon-item focusable cfg-row feed-extra');
|
|
1866
1969
|
r.setAttribute('data-fk', 'played'); r.setAttribute('data-ctl', 'extra');
|
|
@@ -1869,48 +1972,9 @@
|
|
|
1869
1972
|
: 'Sources you played rank like any other') + ' · OK to turn ' + (played ? 'off' : 'on')));
|
|
1870
1973
|
r.__onselect = function () { Stremio.setPlayedFirst(!played); refeed('ranking', 'played', 'extra'); };
|
|
1871
1974
|
wrap.appendChild(r);
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
un.setAttribute('data-fk', 'unplayable'); un.setAttribute('data-ctl', 'extra');
|
|
1876
|
-
un.appendChild(U.el('div', 'a-name', 'Sources the TV can’t play: ' + (hideUn ? 'Hidden' : 'Shown')));
|
|
1877
|
-
un.appendChild(U.el('div', 'a-url', (hideUn ? 'Left out (torrents, links it can’t open): direct links only; where-to-watch apps stay'
|
|
1878
|
-
: 'Listed, dimmed, at the bottom (torrents, links it can’t open)') + ' · OK to ' + (hideUn ? 'show them' : 'hide them')));
|
|
1879
|
-
un.__onselect = function () { Stremio.setPref('hideUnplayable', !hideUn); refeed('ranking', 'unplayable', 'extra'); };
|
|
1880
|
-
wrap.appendChild(un);
|
|
1881
|
-
// skip the source list: Play plays the top source at once
|
|
1882
|
-
var skipList = Stremio.getPrefs().skipSourceList;
|
|
1883
|
-
var sl = U.el('div', 'addon-item focusable cfg-row feed-extra');
|
|
1884
|
-
sl.setAttribute('data-fk', 'skiplist'); sl.setAttribute('data-ctl', 'extra');
|
|
1885
|
-
sl.appendChild(U.el('div', 'a-name', 'Skip the source list: ' + (skipList ? 'On' : 'Off')));
|
|
1886
|
-
sl.appendChild(U.el('div', 'a-url', (skipList ? '▶ Play, OK on an episode and ▶ Play SxEx play the top source at once (hold OK on an episode › Details for the list)'
|
|
1887
|
-
: '▶ Play and OK on an episode show the sources to pick from') + ' · OK to turn ' + (skipList ? 'off' : 'on')));
|
|
1888
|
-
sl.__onselect = function () { Stremio.setPref('skipSourceList', !skipList); refeed('ranking', 'skiplist', 'extra'); };
|
|
1889
|
-
wrap.appendChild(sl);
|
|
1890
|
-
// a source that doesn't start: the next one
|
|
1891
|
-
var nextOn = Stremio.getPrefs().nextOnFail;
|
|
1892
|
-
var nx = U.el('div', 'addon-item focusable cfg-row feed-extra');
|
|
1893
|
-
nx.setAttribute('data-fk', 'nextonfail'); nx.setAttribute('data-ctl', 'extra');
|
|
1894
|
-
nx.appendChild(U.el('div', 'a-name', 'The next source if one doesn’t start: ' + (nextOn ? 'On' : 'Off')));
|
|
1895
|
-
nx.appendChild(U.el('div', 'a-url', (nextOn ? 'A source the TV refuses, or that hasn’t started after 45 s, gives way to the next best one (up to 3 more)'
|
|
1896
|
-
: 'A source that doesn’t start takes you back to the list') + ' · OK to turn ' + (nextOn ? 'off' : 'on')));
|
|
1897
|
-
nx.__onselect = function () { Stremio.setPref('nextOnFail', !nextOn); refeed('ranking', 'nextonfail', 'extra'); };
|
|
1898
|
-
wrap.appendChild(nx);
|
|
1899
|
-
// size limits: movies and series apart
|
|
1900
|
-
[['movie', 'Movies’ sizes'], ['series', 'Series’ sizes (an episode)']].forEach(function (k) { wrap.appendChild(sizeRow(k[0], k[1])); });
|
|
1901
|
-
// your words: sources with one of them at the bottom, for every title
|
|
1902
|
-
var words = Stremio.getPrefs().hideWords;
|
|
1903
|
-
var w = U.el('div', 'addon-item focusable cfg-row feed-extra');
|
|
1904
|
-
w.setAttribute('data-fk', 'words'); w.setAttribute('data-ctl', 'extra');
|
|
1905
|
-
w.appendChild(U.el('div', 'a-name', 'Hide sources with these words: ' + (words.length ? words.join(', ') : 'None')));
|
|
1906
|
-
w.appendChild(U.el('div', 'a-url', 'A source with one of them in its name, description or file name (a whole word: “CAM” isn’t in “CAMERA”) goes to the bottom, for every title · OK to change'));
|
|
1907
|
-
w.__onselect = function () {
|
|
1908
|
-
App.openEntry({ title: 'Hide sources with these words', value: words.join(', '), placeholder: 'CAM, HDTS, TELESYNC',
|
|
1909
|
-
hint: 'Words separated by commas, capitals or not. Sources with one go to the bottom and are never picked on their own. Leave it empty for none.',
|
|
1910
|
-
validate: function () { return null; },
|
|
1911
|
-
onConfirm: function (v) { App.back(); Stremio.setHideWords(v); refeed('ranking', 'words', 'extra'); } });
|
|
1912
|
-
};
|
|
1913
|
-
wrap.appendChild(w);
|
|
1975
|
+
wrap.appendChild(rankPageRow('Filters ›', filtersSummary(), 'rankfilters'));
|
|
1976
|
+
wrap.appendChild(rankPageRow('When you press Play ›', onPlaySummary(), 'rankplay'));
|
|
1977
|
+
if (_focusChild === 'rankfilters' || _focusChild === 'rankplay') { _feedFocus = { key: _focusChild, ctl: 'extra' }; _focusChild = null; } // (BACK from one: on its row)
|
|
1914
1978
|
wrap.appendChild(U.el('div', 'formats-group', 'Then, in this order'));
|
|
1915
1979
|
},
|
|
1916
1980
|
after: function (wrap) {
|
|
@@ -1929,7 +1993,94 @@
|
|
|
1929
1993
|
}, function () { wrap.appendChild(U.el('div', 'hint', 'Your addons couldn\'t be read just now.')); });
|
|
1930
1994
|
} }
|
|
1931
1995
|
};
|
|
1932
|
-
//
|
|
1996
|
+
// Source ranking's two pages (rankfilters, rankplay): a row on it that opens one, saying
|
|
1997
|
+
// what's set there.
|
|
1998
|
+
function rankPageRow(title, summary, section) {
|
|
1999
|
+
var r = U.el('div', 'addon-item focusable cfg-row feed-extra');
|
|
2000
|
+
r.setAttribute('data-fk', section); r.setAttribute('data-ctl', 'extra');
|
|
2001
|
+
r.appendChild(U.el('div', 'a-name', title));
|
|
2002
|
+
r.appendChild(U.el('div', 'a-url', summary));
|
|
2003
|
+
r.__onselect = function () { _settingsSection = section; renderSettings(); };
|
|
2004
|
+
return r;
|
|
2005
|
+
}
|
|
2006
|
+
function filtersSummary() {
|
|
2007
|
+
var P = Stremio.getPrefs(), bits = ['Sources the TV can’t play: ' + (P.hideUnplayable ? 'Hidden' : 'Shown')], sizes = [];
|
|
2008
|
+
[['movie', 'Movies'], ['series', 'Series']].forEach(function (k) { var L = P.sizeLimits[k[0]]; if (L.min || L.max || L.perHour) { sizes.push(k[1] + ' ' + sizeSummary(L)); } });
|
|
2009
|
+
bits.push(sizes.length ? sizes.join(', ') : 'No size limits');
|
|
2010
|
+
bits.push(P.hideWords.length ? 'Words: ' + P.hideWords.join(', ') : 'No words');
|
|
2011
|
+
return bits.join(' · ');
|
|
2012
|
+
}
|
|
2013
|
+
function onPlaySummary() {
|
|
2014
|
+
var P = Stremio.getPrefs();
|
|
2015
|
+
return 'Source list: ' + (P.skipSourceList ? 'skipped' : 'shown') + ' · The next source if one doesn’t start: ' + (P.nextOnFail ? 'On' : 'Off');
|
|
2016
|
+
}
|
|
2017
|
+
// A page of its own: its title, a hint, its rows; focus back on the one just changed.
|
|
2018
|
+
function rankPage(wrap, title, hint, rows) {
|
|
2019
|
+
wrap.appendChild(U.el('h2', null, 'Playback › Source ranking › ' + title));
|
|
2020
|
+
wrap.appendChild(U.el('div', 'hint feeds-hint', hint));
|
|
2021
|
+
rows.forEach(function (r) { wrap.appendChild(r); });
|
|
2022
|
+
var pref = null;
|
|
2023
|
+
if (_feedFocus) { pref = rows.filter(function (r) { return r.getAttribute('data-fk') === _feedFocus.key; })[0] || null; _feedFocus = null; }
|
|
2024
|
+
Focus.setScope(scr('settings'), pref || rows[0]);
|
|
2025
|
+
}
|
|
2026
|
+
// Filters: what's left out, or sent to the bottom.
|
|
2027
|
+
function settingsRankFilters(wrap) {
|
|
2028
|
+
rankPage(wrap, 'Filters', 'Sources these leave out, or send to the bottom of the list: never picked on their own.',
|
|
2029
|
+
[unplayableRow(), sizeRow('movie', 'Movies’ sizes'), sizeRow('series', 'Series’ sizes (an episode)'), wordsRow()]);
|
|
2030
|
+
}
|
|
2031
|
+
// When you press Play: whether the list shows, and a source that doesn't start.
|
|
2032
|
+
function settingsRankPlay(wrap) {
|
|
2033
|
+
rankPage(wrap, 'When you press Play', 'What ▶ Play does with the ranked list.', [skipListRow(), nextOnFailRow()]);
|
|
2034
|
+
}
|
|
2035
|
+
// direct links only: the sources the TV can't play left out of the list
|
|
2036
|
+
function unplayableRow() {
|
|
2037
|
+
var hideUn = Stremio.getPrefs().hideUnplayable;
|
|
2038
|
+
var un = U.el('div', 'addon-item focusable cfg-row feed-extra');
|
|
2039
|
+
un.setAttribute('data-fk', 'unplayable'); un.setAttribute('data-ctl', 'extra');
|
|
2040
|
+
un.appendChild(U.el('div', 'a-name', 'Sources the TV can’t play: ' + (hideUn ? 'Hidden' : 'Shown')));
|
|
2041
|
+
un.appendChild(U.el('div', 'a-url', (hideUn ? 'Left out (torrents, links it can’t open): direct links only; where-to-watch apps stay'
|
|
2042
|
+
: 'Listed, dimmed, at the bottom (torrents, links it can’t open)') + ' · OK to ' + (hideUn ? 'show them' : 'hide them')));
|
|
2043
|
+
un.__onselect = function () { Stremio.setPref('hideUnplayable', !hideUn); refeed('ranking', 'unplayable', 'extra'); };
|
|
2044
|
+
return un;
|
|
2045
|
+
}
|
|
2046
|
+
// skip the source list: Play plays the top source at once
|
|
2047
|
+
function skipListRow() {
|
|
2048
|
+
var skipList = Stremio.getPrefs().skipSourceList;
|
|
2049
|
+
var sl = U.el('div', 'addon-item focusable cfg-row feed-extra');
|
|
2050
|
+
sl.setAttribute('data-fk', 'skiplist'); sl.setAttribute('data-ctl', 'extra');
|
|
2051
|
+
sl.appendChild(U.el('div', 'a-name', 'Skip the source list: ' + (skipList ? 'On' : 'Off')));
|
|
2052
|
+
sl.appendChild(U.el('div', 'a-url', (skipList ? '▶ Play, OK on an episode and ▶ Play SxEx play the top source at once (hold OK on an episode › Details for the list)'
|
|
2053
|
+
: '▶ Play and OK on an episode show the sources to pick from') + ' · OK to turn ' + (skipList ? 'off' : 'on')));
|
|
2054
|
+
sl.__onselect = function () { Stremio.setPref('skipSourceList', !skipList); refeed('ranking', 'skiplist', 'extra'); };
|
|
2055
|
+
return sl;
|
|
2056
|
+
}
|
|
2057
|
+
// a source that doesn't start: the next one
|
|
2058
|
+
function nextOnFailRow() {
|
|
2059
|
+
var nextOn = Stremio.getPrefs().nextOnFail;
|
|
2060
|
+
var nx = U.el('div', 'addon-item focusable cfg-row feed-extra');
|
|
2061
|
+
nx.setAttribute('data-fk', 'nextonfail'); nx.setAttribute('data-ctl', 'extra');
|
|
2062
|
+
nx.appendChild(U.el('div', 'a-name', 'The next source if one doesn’t start: ' + (nextOn ? 'On' : 'Off')));
|
|
2063
|
+
nx.appendChild(U.el('div', 'a-url', (nextOn ? 'A source the TV refuses, or that hasn’t started after 45 s, gives way to the next best one (up to 3 more)'
|
|
2064
|
+
: 'A source that doesn’t start takes you back to the list') + ' · OK to turn ' + (nextOn ? 'off' : 'on')));
|
|
2065
|
+
nx.__onselect = function () { Stremio.setPref('nextOnFail', !nextOn); refeed('ranking', 'nextonfail', 'extra'); };
|
|
2066
|
+
return nx;
|
|
2067
|
+
}
|
|
2068
|
+
// your words: sources with one of them at the bottom, for every title
|
|
2069
|
+
function wordsRow() {
|
|
2070
|
+
var words = Stremio.getPrefs().hideWords;
|
|
2071
|
+
var w = U.el('div', 'addon-item focusable cfg-row feed-extra');
|
|
2072
|
+
w.setAttribute('data-fk', 'words'); w.setAttribute('data-ctl', 'extra');
|
|
2073
|
+
w.appendChild(U.el('div', 'a-name', 'Hide sources with these words: ' + (words.length ? words.join(', ') : 'None')));
|
|
2074
|
+
w.appendChild(U.el('div', 'a-url', 'A source with one of them in its name, description or file name (a whole word: “CAM” isn’t in “CAMERA”) goes to the bottom, for every title · OK to change'));
|
|
2075
|
+
w.__onselect = function () {
|
|
2076
|
+
App.openEntry({ title: 'Hide sources with these words', value: words.join(', '), placeholder: 'CAM, HDTS, TELESYNC',
|
|
2077
|
+
hint: 'Words separated by commas, capitals or not. Sources with one go to the bottom and are never picked on their own. Leave it empty for none.',
|
|
2078
|
+
validate: function () { return null; },
|
|
2079
|
+
onConfirm: function (v) { App.back(); Stremio.setHideWords(v); refeed('ranking', 'words', 'extra'); } });
|
|
2080
|
+
};
|
|
2081
|
+
return w;
|
|
2082
|
+
}
|
|
2083
|
+
// Size limits (Settings › Source ranking › Filters): smallest, largest and most per hour, for movies or
|
|
1933
2084
|
// series; OK on the row: which one, then its value.
|
|
1934
2085
|
var SIZE_CHOICES = {
|
|
1935
2086
|
min: { label: 'Smallest', none: 'None', values: [0.1, 0.3, 0.5, 1, 2] },
|
package/js/views.js
CHANGED
|
@@ -7,13 +7,14 @@ var Views = (function (V) {
|
|
|
7
7
|
return {
|
|
8
8
|
renderHome: V.renderHome, renderDetail: V.renderDetail, applyAppearance: V.applyAppearance,
|
|
9
9
|
renderPerson: V.renderPerson, detailSnapshot: V.detailSnapshot, restoreDetail: V.restoreDetail, lightSnapshot: V.lightSnapshot, renderSettings: V.renderSettings,
|
|
10
|
-
settingsRoot: V.settingsRoot, backInSettings: V.backInSettings,
|
|
10
|
+
settingsRoot: V.settingsRoot, backInSettings: V.backInSettings, guideBackOn: V.guideBackOn,
|
|
11
11
|
// a screen left: its downloads still running (a title page's details, Discover's
|
|
12
12
|
// pages, a search's rows) stopped
|
|
13
13
|
leaveScreen: function (name) { var f = { detail: V.leaveDetail, discover: V.leaveDiscover, search: V.leaveSearch }[name]; if (f) { f(); } }, backInLocal: V.backInLocal, setHomeTab: V.setHomeTab,
|
|
14
14
|
renderLive: V.renderLive,
|
|
15
|
-
renderSearch: V.renderSearch, renderDiscover: V.renderDiscover, renderEntry: V.renderEntry, backInDetail: V.backInDetail,
|
|
15
|
+
renderSearch: V.renderSearch, searchFor: function (q) { V.searchFor(q); }, renderDiscover: V.renderDiscover, renderEntry: V.renderEntry, backInDetail: V.backInDetail,
|
|
16
16
|
homeDirty: V.homeDirty, refreshHomeIfStale: V.refreshHomeIfStale, alertNewEpisodes: V.alertNewEpisodes, previewTiles: V.previewTiles, withShowInfo: V.withShowInfo,
|
|
17
|
-
isModalOpen: V.isModalOpen, closeModal: V.closeModal, moveKey: V.moveKey, availableTabs: V.availableTabs
|
|
17
|
+
isModalOpen: V.isModalOpen, closeModal: V.closeModal, moveKey: V.moveKey, availableTabs: V.availableTabs,
|
|
18
|
+
menuItems: V.menuItems, menuOrder: V.menuOrder, moveMenuItem: V.moveMenuItem
|
|
18
19
|
};
|
|
19
20
|
})(ViewParts);
|
package/js/ytplay.js
CHANGED
|
@@ -62,12 +62,16 @@ var YTPlay = (function () {
|
|
|
62
62
|
}
|
|
63
63
|
// A live HLS channel whose sound the TV's player stutters on (its server cuts the AAC sound
|
|
64
64
|
// mid-frame): the service looks at it and, if so, gives a relay that repacks the sound:
|
|
65
|
-
// { fix, sure, why, url
|
|
66
|
-
// h: the channel's own User-Agent /
|
|
67
|
-
|
|
65
|
+
// { fix, sure, why, url, cc: [{ ch, lang }] (its closed captions), ccSure }. force: the relay
|
|
66
|
+
// at once (the channel's already known to need it). h: the channel's own User-Agent /
|
|
67
|
+
// Referer ({ ua, ref }). opts (with force): { fix: false (the sound as it is), cc: the caption
|
|
68
|
+
// channels to add as subtitle tracks }.
|
|
69
|
+
function liveOpen(url, h, force, opts) {
|
|
68
70
|
if (MODE === 'browser') { return Promise.reject(new Error('no service')); }
|
|
71
|
+
var cc = opts && opts.cc ? opts.cc.map(function (c) { return c.ch + (c.lang ? '-' + c.lang : ''); }).join(',') : '';
|
|
69
72
|
return ensureService().then(entryAuth).then(function (a) {
|
|
70
|
-
var q = '/live/open?u=' + encodeURIComponent(url) + (h && h.ua ? '&ua=' + encodeURIComponent(h.ua) : '') + (h && h.ref ? '&ref=' + encodeURIComponent(h.ref) : '') + (force ? '&force=1' : '')
|
|
73
|
+
var q = '/live/open?u=' + encodeURIComponent(url) + (h && h.ua ? '&ua=' + encodeURIComponent(h.ua) : '') + (h && h.ref ? '&ref=' + encodeURIComponent(h.ref) : '') + (force ? '&force=1' : '') +
|
|
74
|
+
(force && opts && opts.fix === false ? '&fix=0' : '') + (force && cc ? '&cc=' + encodeURIComponent(cc) : '');
|
|
71
75
|
return U.getJSON(PROXY + q + a.q, force ? 5000 : 30000, 65536, null, a.h);
|
|
72
76
|
});
|
|
73
77
|
}
|
|
@@ -91,6 +95,50 @@ var YTPlay = (function () {
|
|
|
91
95
|
return U.getJSON(PROXY + '/aiom/addons?base=' + encodeURIComponent(base) + a.q, 40000, 1048576, null, h);
|
|
92
96
|
});
|
|
93
97
|
}
|
|
98
|
+
// A playlist's TV guide, read and kept by the service (see service/index.js, epgControl):
|
|
99
|
+
// epgLoad asks it to read it for these channels (k: their keys as a bitset, EPG.bitsFor) ->
|
|
100
|
+
// { state, at, channels, programmes, partial, error }; epgGet: their programs -> { <key>:
|
|
101
|
+
// [[start, stop, title, desc]] }. Both POSTs, what's asked in the body: a newer TV's service
|
|
102
|
+
// reads 8 KB of a request's address and headers, less than thousands of channels' keys.
|
|
103
|
+
// (a POST refused, a service from before it took them or one without the token: asked
|
|
104
|
+
// again as a GET, when it fits in the address)
|
|
105
|
+
// An app that can't read the service's token (its header instead): the guide ticket, asked
|
|
106
|
+
// for once, given with the POSTs (the TV adds an Origin to them, which the header alone doesn't pass).
|
|
107
|
+
var _ticket = null;
|
|
108
|
+
function epgTicket(a) {
|
|
109
|
+
if (a.q || MODE === 'tizenbrew') { return Promise.resolve(a.q); } // (the token; TizenBrew's origin passes as it is)
|
|
110
|
+
if (!_ticket) {
|
|
111
|
+
_ticket = U.getJSON(PROXY + '/epg/ticket', 8000, 4096, null, a.h).then(function (r) {
|
|
112
|
+
return r && /^[0-9a-f]{48}$/.test(r.ticket || '') ? '&ticket=' + r.ticket : '';
|
|
113
|
+
}, function () { _ticket = null; return ''; });
|
|
114
|
+
}
|
|
115
|
+
return _ticket;
|
|
116
|
+
}
|
|
117
|
+
function epgAsk(path, body, maxBytes) {
|
|
118
|
+
if (MODE === 'browser') { return Promise.reject(new Error('no service')); }
|
|
119
|
+
return ensureService().then(entryAuth).then(function (a) {
|
|
120
|
+
return epgTicket(a).then(function (cred) { return { a: a, cred: cred }; });
|
|
121
|
+
}).then(function (x) {
|
|
122
|
+
var a = x.a, q = x.cred ? '?' + x.cred.replace(/^&/, '') : '';
|
|
123
|
+
return U.postJSON(PROXY + path + q, body, 15000, maxBytes, a.h).then(null, function (err) {
|
|
124
|
+
if (err && /^HTTP 403\b/.test(err.message || '') && /ticket=/.test(q)) { _ticket = null; } // (the service started again since: a new ticket next time)
|
|
125
|
+
if (!(err && /^HTTP (403|405)\b/.test(err.message || '') && body.length < 6000)) { throw err; }
|
|
126
|
+
return U.getJSON(PROXY + path + (q ? q + '&' : '?') + body, 15000, maxBytes, null, a.h);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// Settings › Storage: the saved guides ({ count, bytes }), and deleting them all.
|
|
131
|
+
function epgFiles() {
|
|
132
|
+
if (MODE === 'browser') { return Promise.resolve(null); }
|
|
133
|
+
return ensureService().then(entryAuth).then(function (a) { return U.getJSON(PROXY + '/epg/files' + (a.q ? '?' + a.q.replace(/^&/, '') : ''), 8000, 4096, null, a.h); });
|
|
134
|
+
}
|
|
135
|
+
function epgClear() { return epgAsk('/epg/clear', '', 4096); }
|
|
136
|
+
function epgLoad(u, k, force) {
|
|
137
|
+
return epgAsk('/epg/load', 'u=' + encodeURIComponent(u) + '&k=' + k + (force ? '&force=1' : ''), 65536);
|
|
138
|
+
}
|
|
139
|
+
function epgGet(u, keys, from, to, max) {
|
|
140
|
+
return epgAsk('/epg/get', 'u=' + encodeURIComponent(u) + '&ids=' + encodeURIComponent(keys.join('|')) + '&from=' + from + '&to=' + to + (max ? '&max=' + max : ''), 4194304);
|
|
141
|
+
}
|
|
94
142
|
// Whether YouTube gives the video out to the service (it refuses some uploads to the player
|
|
95
143
|
// the service asks as): false when it says no; true when it does, or no answer came (the
|
|
96
144
|
// player tries). The service keeps what it found, so playing it next asks YouTube nothing new.
|
|
@@ -181,6 +229,6 @@ var YTPlay = (function () {
|
|
|
181
229
|
return MODE === 'tizenbrew' ? 'TizenBrew module' : MODE === 'wgt' ? 'Sideloaded app (.wgt)' : 'Browser (development)';
|
|
182
230
|
}
|
|
183
231
|
|
|
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; },
|
|
232
|
+
return { ensureService: ensureService, resolverUrl: resolverUrl, infoUrl: infoUrl, canPlay: canPlay, liveOpen: liveOpen, hlsMaster: hlsMaster, aiomAddons: aiomAddons, epgLoad: epgLoad, epgGet: epgGet, epgFiles: epgFiles, epgClear: epgClear, entryOpen: entryOpen, entryPoll: entryPoll, entryClose: entryClose, subsUrl: subsUrl, audioLang: audioLang, setAudioLang: setAudioLang, installMode: function () { return MODE; },
|
|
185
233
|
installLabel: installLabel, unavailableMessage: unavailableMessage, isRunning: isRunning, port: PORT };
|
|
186
234
|
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stremiok",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "StremioK streaming client for Tizen 3.0+ Samsung TVs (TizenBrew app module).",
|
|
5
5
|
"packageType": "app",
|
|
6
6
|
"appName": "StremioK",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"icon.png",
|
|
15
15
|
"config.xml",
|
|
16
16
|
"README.md",
|
|
17
|
+
"CHANGELOG.md",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
],
|
|
19
20
|
"keys": [
|