-
-
Notifications
You must be signed in to change notification settings - Fork 66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open Jellyfin in iframe to fix scrolling in webOS 3 #14
Conversation
The trick with passing object into iframe didn't work for Tizen - object defined, but its content is not. CORS I think. |
I wonder if this would be allowed by LG, but we'll cross that bridge when we get there. |
I think we can add this override in jf-web by inspecting if injected |
I tried to load I did not find the "right" way to capture "ASAP" inject var contentFrame = document.querySelector('#contentFrame');
var contentWindow = contentFrame.contentWindow;
var timer;
var loaded = false;
function onLoad() {
if (loaded) {
return;
}
loaded = true;
clearInterval(timer);
contentFrame.contentDocument.removeEventListener('DOMContentLoaded', onLoad);
console.log('WebOS adapter');
contentWindow.webOS = webOS;
}
var domAttached = false;
contentWindow.addEventListener('unload', function() {
timer = setInterval(function () {
switch (contentFrame.contentDocument.readyState) {
case 'loading':
if (!domAttached) {
contentFrame.contentDocument.addEventListener('DOMContentLoaded', onLoad);
domAttached = true;
} else { // Securely connected to DOMContentLoaded - can stop timer
clearInterval(timer);
}
break;
// In the case of "loading" is not caught
case 'interactive':
onLoad();
break;
}
}, 0);
});
// In the case of "loading" and "interactive" are not caught
contentFrame.addEventListener('load', onLoad); |
Yeah, doing it in |
a32e355 (simplified this a bit) Important note (also added in the first message) |
Do you have any idea why? unrelated note: why don't you join us on matrix? |
Because I "exit app" ( |
We can ship our own copy of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hopefully you tested that :)
org.jellyfin.webos/js/index.js
Outdated
function injectScript(document, url) { | ||
var xhr = new XMLHttpRequest(); | ||
xhr.open('GET', url); | ||
xhr.onload = function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't we at least alert()
a user if xhr failed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alert
is not displayed. I can probably try to preload the injected script before actually connecting to the server, and then displayError
in case of xhr failure.
@JustAMan This PR can create problems for those who have frame-restrictive proxies - jellyfin/jellyfin-web#675 (comment) |
@dmitrylyzo why do you doubt it? |
Someone could just deny frames, and then app won't work. |
That would be their problem, no? :) Jumping through lots of extra hoops feels too much here. |
Tried to add backward compatibility. in webOS adapter - won't work with `NativeShell` (when we create it) because it will return its version instead of `jellyfin-web`. Or just need to add an extra function (to retrieve `jellyfin-web` version) in `appHost`.diff --git a/org.jellyfin.webos/js/webOS.js b/org.jellyfin.webos/js/webOS.js
index 8fde4e7..fa19178 100644
--- a/org.jellyfin.webos/js/webOS.js
+++ b/org.jellyfin.webos/js/webOS.js
@@ -3,6 +3,43 @@
console.log('WebOS adapter');
+ /**
+ * Compares two versions.
+ * X.Y is less than X.Y.0
+ *
+ * @param {string} v1 version 1
+ * @param {string} v2 version 2
+ * @returns {number} -1 v1 < v2
+ * @returns {number} 0 v1 = v2
+ * @returns {number} 1 v1 > v2
+ */
+ function versionCompare(v1, v2) {
+ if (typeof v1 !== 'string' || typeof v2 !== 'string') {
+ throw new TypeError('Versions must be strings');
+ }
+
+ var arr1 = v1.split('.');
+ var arr2 = v2.split('.');
+
+ for (var i = 0, n = Math.max(arr1.length, arr2.length); i < n; i++) {
+ if (i >= arr1.length && i < arr2.length) {
+ return -1;
+ } else if (i < arr1.length && i >= arr2.length) {
+ return 1;
+ } else {
+ var num1 = parseInt(arr1[i]);
+ var num2 = parseInt(arr2[i]);
+ if (num1 < num2) {
+ return -1;
+ } else if (num1 > num2) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+ }
+
function postMessage(type, data) {
window.top.postMessage({
type: type,
@@ -15,4 +52,52 @@
postMessage('AppHost.exit');
}
};
+
+ window.addEventListener('load', function () {
+ require(['apphost'], function(appHost) {
+ console.log('Version: ' + appHost.appVersion());
+
+ // FIXME: Need a jellyfin-web version - won't work with NativeShell
+ if (versionCompare(appHost.appVersion(), '10.5') === -1) {
+ console.log("Turn on 'Back' button handling");
+
+ var historyDepth = 1;
+
+ document.addEventListener('keydown', function (e) {
+ switch (e.keyCode) {
+ // Back
+ case 461:
+ if (historyDepth > 1) {
+ history.back();
+ } else {
+ webOS.platformBack();
+ }
+ break;
+ }
+ });
+
+ window.addEventListener('pushState', function(e) {
+ historyDepth++;
+ });
+
+ window.addEventListener('popstate', function() {
+ historyDepth--;
+ });
+
+ // Add 'pushState' and 'replaceState' events
+ var _wr = function(type) {
+ var orig = history[type];
+ return function() {
+ var rv = orig.apply(this, arguments);
+ var e = new Event(type);
+ e.arguments = arguments;
+ window.dispatchEvent(e);
+ return rv;
+ };
+ };
+ history.pushState = _wr('pushState');
+ history.replaceState = _wr('replaceState');
+ }
+ });
+ });
})(); or in separate module - no script caching (can be added); actually checks server version instead of `jellyfin-web`--- /dev/null 2020-01-28 08:44:40.831999968 +0300
+++ b/org.jellyfin.webos/js/backbutton.js 2020-01-28 20:35:26.000000000 +0300
@@ -0,0 +1,47 @@
+(function() {
+ 'use strict';
+
+ console.log("Turn on 'Back' button handling");
+
+ window.addEventListener('load', function () {
+ var historyDepth = 1;
+
+ document.addEventListener('keydown', function (e) {
+ switch (e.keyCode) {
+ // Back
+ case 461:
+ if (historyDepth > 1) {
+ history.back();
+ } else {
+ webOS.platformBack();
+ }
+ break;
+ }
+ });
+
+ window.addEventListener('pushState', function(e) {
+ historyDepth++;
+ });
+
+ window.addEventListener('popstate', function() {
+ historyDepth--;
+ });
+
+ // Add 'pushState' and 'replaceState' events
+ var _wr = function(type) {
+ var orig = history[type];
+ return function() {
+ var rv = orig.apply(this, arguments);
+ var e = new Event(type);
+ e.arguments = arguments;
+ window.dispatchEvent(e);
+ return rv;
+ };
+ };
+ history.pushState = _wr('pushState');
+ history.replaceState = _wr('replaceState');
+ });
+})();
diff --git a/org.jellyfin.webos/js/index.js b/org.jellyfin.webos/js/index.js
index 05c3168..7b5e707 100644
--- a/org.jellyfin.webos/js/index.js
+++ b/org.jellyfin.webos/js/index.js
@@ -1,7 +1,6 @@
var curr_req = false;
var server_info = false;
var manifest = false;
-var textToInject = false;
//Adds .includes to string to do substring matching
if (!String.prototype.includes) {
@@ -16,6 +15,43 @@ if (!String.prototype.includes) {
};
}
+/**
+ * Compares two versions.
+ * X.Y is less than X.Y.0
+ *
+ * @param {string} v1 version 1
+ * @param {string} v2 version 2
+ * @returns {number} -1 v1 < v2
+ * @returns {number} 0 v1 = v2
+ * @returns {number} 1 v1 > v2
+ */
+function versionCompare(v1, v2) {
+ if (typeof v1 !== 'string' || typeof v2 !== 'string') {
+ throw new TypeError('Versions must be strings');
+ }
+
+ var arr1 = v1.split('.');
+ var arr2 = v2.split('.');
+
+ for (var i = 0, n = Math.max(arr1.length, arr2.length); i < n; i++) {
+ if (i >= arr1.length && i < arr2.length) {
+ return -1;
+ } else if (i < arr1.length && i >= arr2.length) {
+ return 1;
+ } else {
+ var num1 = parseInt(arr1[i]);
+ var num2 = parseInt(arr2[i]);
+ if (num1 < num2) {
+ return -1;
+ } else if (num1 > num2) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
function isVisible(element) {
return element.offsetWidth > 0 && element.offsetHeight > 0;
@@ -218,7 +254,7 @@ function handleSuccessServerInfo(data, baseurl, auto_connect) {
}
}
- storage.set('connected_server', { 'baseurl': baseurl, 'auto_connect': auto_connect, 'id': data.Id })
+ storage.set('connected_server', { 'baseurl': baseurl, 'auto_connect': auto_connect, 'id': data.Id, 'version': data.Version })
getManifest(baseurl)
}
@@ -238,8 +274,8 @@ function handleSuccessManifest(data, baseurl) {
storage.set('connected_server', info)
console.log(info);
- getTextToInject().then(function () {
- handoff(hosturl);
+ getTextToInject(info.version).then(function (textToInject) {
+ handoff(hosturl, textToInject);
}).catch(function (error) {
console.error(error);
displayError(error);
@@ -281,29 +317,45 @@ function abort() {
console.log("Aborting...");
}
-function getTextToInject() {
- var url = 'js/webOS.js';
-
+function loadUrl(url) {
return new Promise(function (resolve, reject) {
- if (textToInject) {
- resolve(textToInject);
- } else {
- var xhr = new XMLHttpRequest();
+ var xhr = new XMLHttpRequest();
- xhr.open('GET', url);
+ xhr.open('GET', url);
- xhr.onload = function () {
- textToInject = xhr.responseText;
- resolve(textToInject);
- };
+ xhr.onload = function () {
+ resolve(xhr.responseText);
+ };
- xhr.onerror = function () {
- reject("Failed to load '" + url + "'");
- }
-
- xhr.send();
+ xhr.onerror = function () {
+ reject("Failed to load '" + url + "'");
}
+
+ xhr.send();
+ });
+}
+
+function getTextToInject(version) {
+ var textToInject = '';
+
+ var urls = ['js/webOS.js'];
+
+ // Add 'Back' button handling for older versions
+ if (versionCompare(version, '10.5') === -1) {
+ urls.push('js/backbutton.js');
+ }
+
+ var p = Promise.resolve();
+
+ urls.forEach(function (url) {
+ p = p.then(function () {
+ return loadUrl(url);
+ }).then(function (data) {
+ return textToInject += data;
+ });
});
+
+ return p;
}
function injectScriptText(document, text) {
@@ -313,7 +365,7 @@ function injectScriptText(document, text) {
document.head.appendChild(script);
}
-function handoff(url) {
+function handoff(url, textToInject) {
console.log("Handoff called with: ", url)
//hideConnecting();
|
I think getting back to the WebOS menu from the home screen is a nicer (and more expected) experience than getting back to the server input form. Other apps act that way, as well, and people are more likely to want to get back to the home menu than to change server often. Imo, adding a "Change server" entry to the user menu on WebOS that would bring the user back to the initial form is a better solution for that use case. |
In We can add some feature (or other way) to indicate that selectserver should be performed by app and |
I'm fine with any way of adding ability to choose a server, but I think showing app's page is slightly better because it allows user to change what they set and then set a "remember this address" checkbox. |
Switched to Requires PR jellyfin/jellyfin-web#755 In my opinion, only a question of support for Jellyfin 10.4.x and below is remained. |
I think we can safely assume that no it will ship with 10.5 and 10.4 is not supported. We can even add a check in the current manifest download on the webOS side. |
I tried to add backward compatibility to the app here, but this is to much for single version which will be replaced soon (I hope). If support for 10.4 will be required, it is easier to add "Back" handling to web instead. For now, I think with PR jellyfin/jellyfin-web#755 it is ready for use with "nightly". Just to let you know
These events aren't sent to the frame. They are not in use. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you test this on webOS 4.1? (2018 OLEDs) Otherwise I'll give it a go tonight. Code looks good.
I have only emulators. I tested 3.0 and 4.0 |
Alright I'll do it tonight (GMT+1). |
|
This PR requires "Go back" handling in
jellyfin-web
and have no backward compatibility.Perhaps backward compatibility can be added by checking the version less than 10.5.0 and performing
history.back
on framecontentWindow
.Opening Jellyfin in iframe fixes scrolling issue in webOS 3. But then handling of "Back" button in iframe is broken. So, we have to handle it manually (almost finished).
Also, in this PR I pass
webOS
NativeShell
object intojellyfin-web
to be able to "exit" in platform-specific way.Inject CSS to hide scrollbar.
Some flawsA vertical scrollbar appears. Addingscrolling="no"
to iframe makes it unscrollable by wheel (but scrollable by ScrollManager). A possible solution (if scrollbar should be hidden) is to add injellyfin-web
something like:But non-app TV-layout users will be affected too.Important noteAfter loading JF (in the frame), you cannot return to the server form (for example, to change server) without restarting the application.