nuvio-tizen 1.9.6 → 1.9.7

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/config.xml CHANGED
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <widget xmlns="http://www.w3.org/ns/widgets" xmlns:tizen="http://tizen.org/ns/widgets"
3
- id="http://nuvio.tizen/NuvioTizen" version="1.9.6" viewmodes="maximized">
3
+ id="http://nuvio.tizen/NuvioTizen" version="1.9.7" viewmodes="maximized">
4
4
  <tizen:application id="Nuvio00001.Nuvio" package="Nuvio00001" required_version="3.0"/>
5
5
  <content src="index.html"/>
6
6
  <feature name="http://tizen.org/feature/screen.size.all"/>
package/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=1920, height=1080, user-scalable=no">
6
6
  <title>Nuvio</title>
7
- <link rel="stylesheet" href="css/style.css?v=1.9.6">
7
+ <link rel="stylesheet" href="css/style.css?v=1.9.7">
8
8
  </head>
9
9
  <body>
10
10
  <!-- CSS-applied sentinel (kept off-screen); app.js waits for its width before rendering -->
@@ -50,15 +50,15 @@
50
50
 
51
51
  <!-- Load order matters: polyfills -> util -> iptv -> data -> input -> player -> ytplay -> views -> app -->
52
52
  <!-- ?v= busts the TV webview cache on every release (keep in sync with package.json) -->
53
- <script src="js/polyfills.js?v=1.9.6"></script>
54
- <script src="js/util.js?v=1.9.6"></script>
55
- <script src="js/iptv.js?v=1.9.6"></script>
56
- <script src="js/stremio.js?v=1.9.6"></script>
57
- <script src="js/keys.js?v=1.9.6"></script>
58
- <script src="js/focus.js?v=1.9.6"></script>
59
- <script src="js/player.js?v=1.9.6"></script>
60
- <script src="js/ytplay.js?v=1.9.6"></script>
61
- <script src="js/views.js?v=1.9.6"></script>
62
- <script src="js/app.js?v=1.9.6"></script>
53
+ <script src="js/polyfills.js?v=1.9.7"></script>
54
+ <script src="js/util.js?v=1.9.7"></script>
55
+ <script src="js/iptv.js?v=1.9.7"></script>
56
+ <script src="js/stremio.js?v=1.9.7"></script>
57
+ <script src="js/keys.js?v=1.9.7"></script>
58
+ <script src="js/focus.js?v=1.9.7"></script>
59
+ <script src="js/player.js?v=1.9.7"></script>
60
+ <script src="js/ytplay.js?v=1.9.7"></script>
61
+ <script src="js/views.js?v=1.9.7"></script>
62
+ <script src="js/app.js?v=1.9.7"></script>
63
63
  </body>
64
64
  </html>
package/js/ytplay.js CHANGED
@@ -1,25 +1,23 @@
1
1
  /* YouTube leanback playback. Global namespace: YTPlay
2
- YouTube-live IPTV channels can't be extracted to a direct URL on-device and
3
- youtube.com/tv can't be framed (X-Frame-Options). So we launch a bundled
4
- localhost proxy (service/index.js) that serves youtube.com/tv with those
5
- headers stripped, and load it in a fullscreen overlay iframe — the same
6
- leanback approach TizenTube uses. See service/index.js for the proxy. */
2
+ YouTube-live IPTV channels can't be extracted to a direct URL on-device. We play
3
+ them through YouTube's own leanback TV app (youtube.com/tv) behind a bundled
4
+ localhost proxy (service/index.js) that strips X-Frame-Options/CSP and injects an
5
+ ad-block userscript the same approach TizenTube uses.
6
+
7
+ IMPORTANT: leanback must run in the app's MAIN FRAME. On Tizen the platform media
8
+ APIs (webapis/AVPlay) that leanback needs to actually decode video are injected only
9
+ into the main frame, never a cross-origin iframe — in an iframe the UI works but the
10
+ picture stays black and Play does nothing. So we navigate the whole webview to the
11
+ proxy (like TizenTube standalone) and pass a return URL; the proxy injects a Back
12
+ handler that navigates back to Nuvio. */
7
13
  var YTPlay = (function () {
8
14
  'use strict';
9
15
 
10
16
  var PROXY = 'http://127.0.0.1:8099'; // 127.0.0.1 (not "localhost") resolves reliably on Tizen
11
- var _open = false;
12
- var _onExit = null;
13
17
  var _idCache = {}; // ytPage -> { id, ts }
14
18
  var ID_TTL = 5 * 60 * 1000; // live video ids rotate; keep short
15
19
 
16
- function overlay() { return U.byId('yt-leanback'); }
17
-
18
- // Resolve once the proxy answers /health. Order:
19
- // 1. Probe /health first — covers a warm service (TV) and a dev run of the proxy
20
- // under Node in a desktop browser (`node service/index.js`).
21
- // 2. On Tizen, if it's not up, launch the <tizen:service> and poll.
22
- // 3. Otherwise (browser, no proxy running) reject with a dev hint.
20
+ // Resolve once the proxy answers /health; launch the bundled <tizen:service> if needed.
23
21
  function ensureService() {
24
22
  return new Promise(function (resolve, reject) {
25
23
  function poll(left) {
@@ -28,9 +26,7 @@ var YTPlay = (function () {
28
26
  setTimeout(function () { poll(left - 1); }, 500);
29
27
  });
30
28
  }
31
- // Already running? (warm TV service, or a locally-run proxy in dev)
32
29
  U.getText(PROXY + '/health', 1500).then(function () { resolve(); }, function () {
33
- // Best-effort launch of the bundled <tizen:service>, then keep polling.
34
30
  var launched = false;
35
31
  try {
36
32
  if (window.tizen && tizen.application && tizen.application.launchAppControl) {
@@ -49,8 +45,7 @@ var YTPlay = (function () {
49
45
  }
50
46
 
51
47
  // A youtube.com/<channel>/live (or /watch?v=) URL -> the current live video id.
52
- // The page is fetched THROUGH the proxy's /cors-bypass so it works both on the TV
53
- // and in a desktop dev browser (youtube.com sends no CORS header of its own).
48
+ // Fetched THROUGH the proxy's /cors-bypass so it works on the TV and in dev.
54
49
  function resolveVideoId(page) {
55
50
  var direct = ('' + page).match(/[?&]v=([A-Za-z0-9_-]{11})/);
56
51
  if (direct) { return Promise.resolve(direct[1]); }
@@ -64,34 +59,7 @@ var YTPlay = (function () {
64
59
  }, function () { return ''; });
65
60
  }
66
61
 
67
- function show(id) {
68
- var o = overlay();
69
- if (!o) { return; }
70
- U.clear(o);
71
- var iframe = document.createElement('iframe');
72
- iframe.setAttribute('allow', 'autoplay; encrypted-media');
73
- iframe.src = PROXY + '/tv#/watch?v=' + encodeURIComponent(id);
74
- iframe.onload = function () { try { iframe.focus(); } catch (e) {} };
75
- o.appendChild(iframe);
76
- o.className = '';
77
- U.byId('app').className = 'hidden';
78
- _open = true;
79
- // Back is handled app-wide by keys.js (tizenhwkey) → App.back() → YTPlay.close(),
80
- // which works even while this cross-origin iframe holds focus.
81
- }
82
-
83
- function close() {
84
- if (!_open) { return; }
85
- _open = false;
86
- var o = overlay();
87
- if (o) { o.className = 'hidden'; U.clear(o); }
88
- U.byId('app').className = '';
89
- var cb = _onExit; _onExit = null;
90
- if (cb) { try { cb(); } catch (e) {} } // redraw detail -> restores focus scope
91
- }
92
-
93
- function open(stream, onExit) {
94
- _onExit = onExit || null;
62
+ function open(stream /*, onExit */) {
95
63
  var page = stream.ytPage || stream.url;
96
64
  U.spinner(true);
97
65
  ensureService().then(function () {
@@ -99,13 +67,22 @@ var YTPlay = (function () {
99
67
  }).then(function (id) {
100
68
  U.spinner(false);
101
69
  if (!id) { U.toast('Could not find the live stream for this channel', 3500); return; }
102
- show(id);
103
- }, function (err) {
70
+ // Navigate the MAIN FRAME to leanback (needed for real playback on Tizen).
71
+ // nvret = where to return on Back; the hash carries the video so youtube.com/tv
72
+ // doesn't 303-redirect and drop our query param.
73
+ var ret = location.href;
74
+ window.location.href = PROXY + '/tv?nvret=' + encodeURIComponent(ret) +
75
+ '#/watch?v=' + encodeURIComponent(id);
76
+ }, function () {
104
77
  U.spinner(false);
105
78
  U.toast('Couldn\'t reach the YouTube proxy (port 8099). Use the sideloaded Nuvio ' +
106
79
  'build so its background service is installed. (Desktop dev: run node service/index.js.)', 5500);
107
80
  });
108
81
  }
109
82
 
110
- return { open: open, close: close, isOpen: function () { return _open; } };
83
+ // Kept for API compatibility (playback now navigates away rather than overlaying).
84
+ function close() {}
85
+ function isOpen() { return false; }
86
+
87
+ return { open: open, close: close, isOpen: isOpen };
111
88
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuvio-tizen",
3
- "version": "1.9.6",
3
+ "version": "1.9.7",
4
4
  "description": "Nuvio-style Stremio-addon streaming client for Tizen 3.0+ Samsung TVs (TizenBrew app module).",
5
5
  "packageType": "app",
6
6
  "appName": "Nuvio",
package/service/index.js CHANGED
@@ -42,10 +42,24 @@ function hostAllowed(h) {
42
42
 
43
43
  function proxyPrefix() { return 'http://localhost:' + PORT + '/cors-bypass/'; }
44
44
 
45
- function rewriteText(text, isTvHtml) {
45
+ // A Back-key handler injected into the leanback page so pressing Return goes back to
46
+ // Nuvio (nvret = the app URL Nuvio navigated from). Runs in leanback's top frame, where
47
+ // tizenhwkey fires. Capture phase + stopImmediatePropagation so leanback doesn't also
48
+ // handle it. Falls back to history.back() if no return URL was supplied.
49
+ function backHandlerScript(nvret) {
50
+ return '<script>(function(){var r=' + JSON.stringify(nvret || '') + ';' +
51
+ 'function go(e){try{e.preventDefault();e.stopImmediatePropagation();}catch(x){}' +
52
+ 'if(r){try{window.location.href=r;return;}catch(x2){}}try{history.back();}catch(x3){}}' +
53
+ 'window.addEventListener("tizenhwkey",function(e){if(e&&e.keyName==="back")go(e);},true);' +
54
+ 'window.addEventListener("keydown",function(e){if(e&&(e.keyCode===10009||e.keyCode===461))go(e);},true);' +
55
+ '})();</script>';
56
+ }
57
+
58
+ function rewriteText(text, isTvHtml, nvret) {
46
59
  var p = proxyPrefix();
47
60
  if (isTvHtml) {
48
- var inject = '<script>window.__tizenTubeStandaloneVersion="nuvio";</script>' +
61
+ var inject = (nvret ? backHandlerScript(nvret) : '') +
62
+ '<script>window.__tizenTubeStandaloneVersion="nuvio";</script>' +
49
63
  '<script src="' + USERSCRIPT_CDN + '?ver=' + Date.now() +
50
64
  '" onerror="this.onerror=null;this.src=\'' + USERSCRIPT_FALLBACK + '\'"></script>';
51
65
  if (/<body[^>]*>/i.test(text)) {
@@ -143,6 +157,20 @@ function handle(req, res) {
143
157
  if (!hostAllowed(pu.hostname)) { res.writeHead(403); res.end('host not allowed'); return; }
144
158
 
145
159
  var isTvHtml = (!isCors && pathOnly === '/tv');
160
+ // Nuvio passes its return URL as ?nvret= on the /tv request so we can inject a
161
+ // Back handler that navigates back to the app.
162
+ var nvret = '';
163
+ if (isTvHtml) {
164
+ var qmatch = req.url.split('?')[1];
165
+ if (qmatch) {
166
+ var parts = qmatch.split('&');
167
+ for (var qi = 0; qi < parts.length; qi++) {
168
+ if (parts[qi].indexOf('nvret=') === 0) {
169
+ try { nvret = decodeURIComponent(parts[qi].slice('nvret='.length)); } catch (e) {}
170
+ }
171
+ }
172
+ }
173
+ }
146
174
 
147
175
  var headers = {};
148
176
  for (var k in req.headers) {
@@ -196,7 +224,7 @@ function handle(req, res) {
196
224
  pres.on('end', function () {
197
225
  decompress(Buffer.concat(chunks), enc, function (err, buf) {
198
226
  if (err) { res.writeHead(502); res.end('decompress error'); return; }
199
- var text = rewriteText(buf.toString('utf8'), isTvHtml);
227
+ var text = rewriteText(buf.toString('utf8'), isTvHtml, nvret);
200
228
  // new Buffer(): Buffer.from() doesn't exist on the TV's old Node (< 4.5).
201
229
  var body = new Buffer(text, 'utf8');
202
230
  outHeaders['Content-Length'] = body.length;