nuvio-tizen 1.4.0 → 1.5.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 +7 -2
- package/css/style.css +4 -1
- package/js/player.js +103 -15
- package/js/views.js +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,8 +20,13 @@ core behaviour, targeting 2017 Samsung TVs (Tizen 3.0, ~Chromium 47).
|
|
|
20
20
|
- **Stream aggregation** across all installed stream addons, each source showing
|
|
21
21
|
**size + languages/description** (from `behaviorHints.videoSize` and the addon's own text,
|
|
22
22
|
e.g. MediaFusion / Torrentio).
|
|
23
|
-
- **In-player menu** (press **UP**):
|
|
24
|
-
|
|
23
|
+
- **In-player menu** (press **UP**):
|
|
24
|
+
- *Select source* — switch stream mid-playback (keeps your position), each source shown
|
|
25
|
+
on two lines (name + size / seeds / languages).
|
|
26
|
+
- *Audio track* — shown only when the stream has more than one audio track
|
|
27
|
+
(AVPlay `getTotalTrackInfo` / `setSelectTrack`).
|
|
28
|
+
- *Configure subtitles* → *Language* (2 levels: language → the tracks within it),
|
|
29
|
+
*Size*, *Style*.
|
|
25
30
|
- **Subtitles from addons**: fetched per title/episode, SRT + VTT parsed and rendered as a
|
|
26
31
|
timed overlay; press **UP** during playback to pick a track (or turn them Off).
|
|
27
32
|
Configurable in Settings: **default language** (auto-loads a matching track),
|
package/css/style.css
CHANGED
|
@@ -116,8 +116,11 @@ html, body {
|
|
|
116
116
|
#sub-menu { position: absolute; right: 60px; bottom: 250px; width: 560px; max-height: 620px; overflow: hidden; z-index: 30; background: rgba(20,25,36,0.98); border-radius: 12px; padding: 18px; }
|
|
117
117
|
#sub-menu .sm-title { font-size: 24px; font-weight: bold; margin-bottom: 12px; color: #c7cbd4; }
|
|
118
118
|
#sub-menu .sm-list { max-height: 520px; overflow: hidden; }
|
|
119
|
-
#sub-menu .sm-item { padding: 12px 16px;
|
|
119
|
+
#sub-menu .sm-item { padding: 12px 16px; border-radius: 8px; border: 3px solid transparent; }
|
|
120
|
+
#sub-menu .sm-item .sm-main { font-size: 22px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
121
|
+
#sub-menu .sm-item .sm-detail { font-size: 16px; color: #9aa2b1; margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
120
122
|
#sub-menu .sm-item.sel { background: #818cf8; color: #0e1117; }
|
|
123
|
+
#sub-menu .sm-item.sel .sm-detail { color: #26304a; }
|
|
121
124
|
|
|
122
125
|
/* --- Search --- */
|
|
123
126
|
.search-wrap { position: absolute; top: 120px; left: 60px; right: 60px; bottom: 40px; }
|
package/js/player.js
CHANGED
|
@@ -43,13 +43,16 @@ var Player = (function () {
|
|
|
43
43
|
var _osdTimer = null;
|
|
44
44
|
var _seekPending = 0;
|
|
45
45
|
|
|
46
|
+
// audio
|
|
47
|
+
var _audioIdx = null; // currently selected audio track index
|
|
48
|
+
|
|
46
49
|
// subtitles
|
|
47
50
|
var _subTracks = []; // [{lang,url,_addon}]
|
|
48
51
|
var _cues = null; // [{start,end,text}]
|
|
49
52
|
var _subOn = false;
|
|
50
53
|
var _subLabel = '';
|
|
54
|
+
var _subTrackUrl = '';
|
|
51
55
|
var _menuOpen = false;
|
|
52
|
-
var _menuIdx = 0;
|
|
53
56
|
|
|
54
57
|
function init() {
|
|
55
58
|
objEl = U.byId('av-player');
|
|
@@ -124,9 +127,10 @@ var Player = (function () {
|
|
|
124
127
|
_isLive = (ctx.type === 'tv') || !!ctx.isLive;
|
|
125
128
|
_ctx.resumeSec = ctx.resumeSec || getResume(ctx.type, ctx.id);
|
|
126
129
|
|
|
127
|
-
// reset subtitle state
|
|
128
|
-
_subTracks = []; _cues = null; _subOn = false; _subLabel = '';
|
|
129
|
-
|
|
130
|
+
// reset subtitle + audio state
|
|
131
|
+
_subTracks = []; _cues = null; _subOn = false; _subLabel = ''; _subTrackUrl = '';
|
|
132
|
+
_audioIdx = null;
|
|
133
|
+
_menuOpen = false;
|
|
130
134
|
subsEl.className = 'hidden'; U.clear(subsEl);
|
|
131
135
|
subMenuEl.className = 'hidden';
|
|
132
136
|
updateHint();
|
|
@@ -192,6 +196,7 @@ var Player = (function () {
|
|
|
192
196
|
try { _dur = avplay.getDuration() / 1000; } catch (e3) { _dur = 0; }
|
|
193
197
|
markLiveFromDuration();
|
|
194
198
|
updateHint();
|
|
199
|
+
try { var atr = getAudioTracks(); _audioIdx = atr.length ? atr[0].index : null; } catch (eA) {}
|
|
195
200
|
if (!_isLive && _ctx.resumeSec > 5) { try { avplay.seekTo(_ctx.resumeSec * 1000); } catch (e4) {} }
|
|
196
201
|
avplay.play();
|
|
197
202
|
renderOSD();
|
|
@@ -309,17 +314,62 @@ var Player = (function () {
|
|
|
309
314
|
U.getText(track.url, 15000).then(function (text) {
|
|
310
315
|
var cues = parseCues(text);
|
|
311
316
|
if (!cues.length) { U.toast('Could not parse subtitles'); return; }
|
|
312
|
-
_cues = cues; _subOn = true; _subLabel = track.lang || 'on';
|
|
317
|
+
_cues = cues; _subOn = true; _subLabel = track.lang || 'on'; _subTrackUrl = track.url;
|
|
313
318
|
renderSubs();
|
|
314
319
|
U.toast('Subtitles: ' + (track.lang || 'on'));
|
|
315
320
|
}, function () { U.toast('Failed to download subtitles'); });
|
|
316
321
|
}
|
|
317
322
|
|
|
318
323
|
function subsOff() {
|
|
319
|
-
_subOn = false; _cues = null; _subLabel = '';
|
|
324
|
+
_subOn = false; _cues = null; _subLabel = ''; _subTrackUrl = '';
|
|
320
325
|
subsEl.className = 'hidden'; U.clear(subsEl);
|
|
321
326
|
}
|
|
322
327
|
|
|
328
|
+
// ---------- audio tracks ----------
|
|
329
|
+
function getAudioTracks() {
|
|
330
|
+
if (useAV) {
|
|
331
|
+
try {
|
|
332
|
+
var all = avplay.getTotalTrackInfo() || [];
|
|
333
|
+
var out = [];
|
|
334
|
+
for (var i = 0; i < all.length; i++) {
|
|
335
|
+
if (('' + all[i].type).toUpperCase() === 'AUDIO') { out.push(all[i]); }
|
|
336
|
+
}
|
|
337
|
+
return out;
|
|
338
|
+
} catch (e) { return []; }
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
var at = videoEl.audioTracks, arr = [];
|
|
342
|
+
if (at) {
|
|
343
|
+
for (var j = 0; j < at.length; j++) {
|
|
344
|
+
arr.push({ index: j, extra_info: JSON.stringify({ language: at[j].language || at[j].label }) });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return arr;
|
|
348
|
+
} catch (e2) { return []; }
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function audioLabel(tr, n) {
|
|
352
|
+
var info = tr.extra_info;
|
|
353
|
+
try { if (typeof info === 'string') { info = JSON.parse(info); } } catch (e) { info = null; }
|
|
354
|
+
var lang = info && (info.language || info.lang);
|
|
355
|
+
var ch = info && (info.channels || info.channel);
|
|
356
|
+
var parts = [];
|
|
357
|
+
parts.push((lang && lang !== 'und' && lang !== '') ? lang : ('Audio ' + (n + 1)));
|
|
358
|
+
if (ch) { parts.push(ch + 'ch'); }
|
|
359
|
+
return parts.join(' · ');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function selectAudio(index) {
|
|
363
|
+
if (useAV) {
|
|
364
|
+
try { avplay.setSelectTrack('AUDIO', index); }
|
|
365
|
+
catch (e) { try { avplay.setSelectTrack('AUDIO', '' + index); } catch (e2) {} }
|
|
366
|
+
} else {
|
|
367
|
+
try { var at = videoEl.audioTracks; for (var i = 0; i < at.length; i++) { at[i].enabled = (i === index); } } catch (e3) {}
|
|
368
|
+
}
|
|
369
|
+
_audioIdx = index;
|
|
370
|
+
U.toast('Audio track changed');
|
|
371
|
+
}
|
|
372
|
+
|
|
323
373
|
function applyStyleNow() {
|
|
324
374
|
var span = subsEl.firstChild;
|
|
325
375
|
if (span) { styleSpan(span); }
|
|
@@ -362,8 +412,10 @@ var Player = (function () {
|
|
|
362
412
|
subMenuEl.appendChild(U.el('div', 'sm-title', m.title));
|
|
363
413
|
var list = U.el('div', 'sm-list');
|
|
364
414
|
m.items.forEach(function (it, i) {
|
|
365
|
-
var
|
|
366
|
-
|
|
415
|
+
var node = U.el('div', 'sm-item' + (i === m.idx ? ' sel' : ''));
|
|
416
|
+
node.appendChild(U.el('div', 'sm-main', it.label + (it.sel ? ' ✓' : '')));
|
|
417
|
+
if (it.detail) { node.appendChild(U.el('div', 'sm-detail', it.detail)); }
|
|
418
|
+
list.appendChild(node);
|
|
367
419
|
});
|
|
368
420
|
subMenuEl.appendChild(list);
|
|
369
421
|
var sel = list.children[m.idx];
|
|
@@ -375,16 +427,32 @@ var Player = (function () {
|
|
|
375
427
|
if (_ctx.streams && _ctx.streams.length) {
|
|
376
428
|
items.push({ label: 'Select source', run: function () { pushMenu(sourceMenu()); } });
|
|
377
429
|
}
|
|
430
|
+
if (getAudioTracks().length > 1) {
|
|
431
|
+
items.push({ label: 'Audio track', run: function () { pushMenu(audioMenu()); } });
|
|
432
|
+
}
|
|
378
433
|
items.push({ label: 'Configure subtitles', run: function () { pushMenu(subConfigMenu()); } });
|
|
379
434
|
return { title: 'Menu', items: items, idx: 0 };
|
|
380
435
|
}
|
|
381
436
|
|
|
382
437
|
function sourceMenu() {
|
|
383
|
-
var
|
|
384
|
-
|
|
438
|
+
var idx = 0;
|
|
439
|
+
var items = _ctx.streams.map(function (s, i) {
|
|
440
|
+
if (s.url === _ctx.url) { idx = i; }
|
|
441
|
+
return { label: s.label || 'Source', detail: s.detail || '', sel: (s.url === _ctx.url),
|
|
385
442
|
run: function () { switchSource(s); closeMenu(); } };
|
|
386
443
|
});
|
|
387
|
-
return { title: 'Select source', items: items, idx:
|
|
444
|
+
return { title: 'Select source', items: items, idx: idx };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function audioMenu() {
|
|
448
|
+
var tracks = getAudioTracks();
|
|
449
|
+
var idx = 0;
|
|
450
|
+
var items = tracks.map(function (tr, i) {
|
|
451
|
+
if (tr.index === _audioIdx) { idx = i; }
|
|
452
|
+
return { label: audioLabel(tr, i), sel: (tr.index === _audioIdx),
|
|
453
|
+
run: function () { selectAudio(tr.index); closeMenu(); } };
|
|
454
|
+
});
|
|
455
|
+
return { title: 'Audio track', items: items, idx: idx };
|
|
388
456
|
}
|
|
389
457
|
|
|
390
458
|
function subConfigMenu() {
|
|
@@ -395,16 +463,36 @@ var Player = (function () {
|
|
|
395
463
|
] };
|
|
396
464
|
}
|
|
397
465
|
|
|
466
|
+
// Level 1: Off + one row per language (with option count).
|
|
398
467
|
function langMenu() {
|
|
399
|
-
var
|
|
468
|
+
var byLang = {}, order = [];
|
|
400
469
|
_subTracks.forEach(function (tr) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
470
|
+
var l = tr.lang || 'unknown';
|
|
471
|
+
if (!byLang[l]) { byLang[l] = []; order.push(l); }
|
|
472
|
+
byLang[l].push(tr);
|
|
473
|
+
});
|
|
474
|
+
var items = [{ label: 'Off', sel: !_subOn, run: function () { subsOff(); closeMenu(); } }];
|
|
475
|
+
order.forEach(function (l) {
|
|
476
|
+
var n = byLang[l].length;
|
|
477
|
+
items.push({ label: l, detail: n + ' option' + (n > 1 ? 's' : ''),
|
|
478
|
+
sel: (_subOn && _subLabel === l),
|
|
479
|
+
run: function () { pushMenu(langTracksMenu(l, byLang[l])); } });
|
|
404
480
|
});
|
|
405
481
|
return { title: 'Subtitle language', items: items, idx: 0 };
|
|
406
482
|
}
|
|
407
483
|
|
|
484
|
+
// Level 2: the individual tracks within one language.
|
|
485
|
+
function langTracksMenu(lang, tracks) {
|
|
486
|
+
var idx = 0;
|
|
487
|
+
var items = tracks.map(function (tr, i) {
|
|
488
|
+
if (_subTrackUrl === tr.url) { idx = i; }
|
|
489
|
+
return { label: (tr._addon || 'Source') + ' · option ' + (i + 1),
|
|
490
|
+
sel: (_subTrackUrl === tr.url),
|
|
491
|
+
run: function () { loadTrack(tr); closeMenu(); } };
|
|
492
|
+
});
|
|
493
|
+
return { title: lang + ' subtitles', items: items, idx: idx };
|
|
494
|
+
}
|
|
495
|
+
|
|
408
496
|
function optionMenu(title, key, opts) {
|
|
409
497
|
var cfg = getSubCfg();
|
|
410
498
|
var idx = 0;
|
package/js/views.js
CHANGED
|
@@ -360,7 +360,15 @@ var Views = (function () {
|
|
|
360
360
|
if (!playable) { U.toast('This source can’t play directly on the TV.'); return; }
|
|
361
361
|
var sources = _d.streams.filter(Stremio.isPlayable).map(function (st) {
|
|
362
362
|
var sz = Stremio.streamSize(st);
|
|
363
|
-
|
|
363
|
+
var desc = Stremio.streamDescription(st).replace(/\n/g, ' ');
|
|
364
|
+
var dparts = [];
|
|
365
|
+
if (sz && !/(TB|GB|MB)\b/i.test(desc)) { dparts.push('💾 ' + sz); }
|
|
366
|
+
if (desc) { dparts.push(desc); }
|
|
367
|
+
return {
|
|
368
|
+
url: st.url,
|
|
369
|
+
label: (st._addon ? st._addon + ' · ' : '') + Stremio.streamName(st),
|
|
370
|
+
detail: dparts.join(' ')
|
|
371
|
+
};
|
|
364
372
|
});
|
|
365
373
|
Player.play({
|
|
366
374
|
url: stream.url, type: _d.type, id: _d.streamId,
|