mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into fonts
This commit is contained in:
commit
ab1799544c
247 changed files with 4602 additions and 8723 deletions
|
@ -1,5 +1,4 @@
|
|||
/* eslint-disable indent */
|
||||
import connectionManager from 'connectionManager';
|
||||
|
||||
class BackdropScreensaver {
|
||||
constructor() {
|
||||
|
@ -21,7 +20,7 @@ class BackdropScreensaver {
|
|||
Limit: 200
|
||||
};
|
||||
|
||||
const apiClient = connectionManager.currentApiClient();
|
||||
const apiClient = window.connectionManager.currentApiClient();
|
||||
apiClient.getItems(apiClient.getCurrentUserId(), query).then((result) => {
|
||||
if (result.Items.length) {
|
||||
import('slideshow').then(({default: Slideshow}) => {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import connectionManager from 'connectionManager';
|
||||
import loading from 'loading';
|
||||
import keyboardnavigation from 'keyboardnavigation';
|
||||
import dialogHelper from 'dialogHelper';
|
||||
|
@ -243,7 +242,7 @@ export class BookPlayer {
|
|||
};
|
||||
|
||||
const serverId = item.ServerId;
|
||||
const apiClient = connectionManager.getApiClient(serverId);
|
||||
const apiClient = window.connectionManager.getApiClient(serverId);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
import('epubjs').then(({default: epubjs}) => {
|
||||
|
|
|
@ -1,229 +1,229 @@
|
|||
define(['events'], function (events) {
|
||||
'use strict';
|
||||
import events from 'events';
|
||||
|
||||
// LinkParser
|
||||
// LinkParser
|
||||
//
|
||||
// https://github.com/ravisorg/LinkParser
|
||||
//
|
||||
// Locate and extract almost any URL within a string. Handles protocol-less domains, IPv4 and
|
||||
// IPv6, unrecognised TLDs, and more.
|
||||
//
|
||||
// This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
// http://creativecommons.org/licenses/by-sa/4.0/
|
||||
(function () {
|
||||
// Original URL regex from the Android android.text.util.Linkify function, found here:
|
||||
// http://stackoverflow.com/a/19696443
|
||||
//
|
||||
// https://github.com/ravisorg/LinkParser
|
||||
// However there were problems with it, most probably related to the fact it was
|
||||
// written in 2007, and it's been highly modified.
|
||||
//
|
||||
// Locate and extract almost any URL within a string. Handles protocol-less domains, IPv4 and
|
||||
// IPv6, unrecognised TLDs, and more.
|
||||
// 1) I didn't like the fact that it was tied to specific TLDs, since new ones
|
||||
// are being added all the time it wouldn't be reasonable to expect developer to
|
||||
// be continually updating their regular expressions.
|
||||
//
|
||||
// This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
// http://creativecommons.org/licenses/by-sa/4.0/
|
||||
(function () {
|
||||
// Original URL regex from the Android android.text.util.Linkify function, found here:
|
||||
// http://stackoverflow.com/a/19696443
|
||||
//
|
||||
// However there were problems with it, most probably related to the fact it was
|
||||
// written in 2007, and it's been highly modified.
|
||||
//
|
||||
// 1) I didn't like the fact that it was tied to specific TLDs, since new ones
|
||||
// are being added all the time it wouldn't be reasonable to expect developer to
|
||||
// be continually updating their regular expressions.
|
||||
//
|
||||
// 2) It didn't allow unicode characters in the domains which are now allowed in
|
||||
// many languages, (including some IDN TLDs). Again these are constantly being
|
||||
// added to and it doesn't seem reasonable to hard-code them. Note this ended up
|
||||
// not being possible in standard JS due to the way it handles multibyte strings.
|
||||
// It is possible using XRegExp, however a big performance hit results. Disabled
|
||||
// for now.
|
||||
//
|
||||
// 3) It didn't allow for IPv6 hostnames
|
||||
// IPv6 regex from http://stackoverflow.com/a/17871737
|
||||
//
|
||||
// 4) It was very poorly commented
|
||||
//
|
||||
// 5) It wasn't as smart as it could have been about what should be part of a
|
||||
// URL and what should be part of human language.
|
||||
// 2) It didn't allow unicode characters in the domains which are now allowed in
|
||||
// many languages, (including some IDN TLDs). Again these are constantly being
|
||||
// added to and it doesn't seem reasonable to hard-code them. Note this ended up
|
||||
// not being possible in standard JS due to the way it handles multibyte strings.
|
||||
// It is possible using XRegExp, however a big performance hit results. Disabled
|
||||
// for now.
|
||||
//
|
||||
// 3) It didn't allow for IPv6 hostnames
|
||||
// IPv6 regex from http://stackoverflow.com/a/17871737
|
||||
//
|
||||
// 4) It was very poorly commented
|
||||
//
|
||||
// 5) It wasn't as smart as it could have been about what should be part of a
|
||||
// URL and what should be part of human language.
|
||||
|
||||
var protocols = '(?:(?:http|https|rtsp|ftp):\\/\\/)';
|
||||
var credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters)
|
||||
+ "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters)
|
||||
+ '\\@)';
|
||||
const protocols = '(?:(?:http|https|rtsp|ftp):\\/\\/)';
|
||||
const credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters)
|
||||
+ "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters)
|
||||
+ '\\@)';
|
||||
|
||||
// IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452
|
||||
// by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
|
||||
// http://intermapper.com/
|
||||
var ipv6 = '('
|
||||
+ '(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
|
||||
+ '|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
|
||||
+ ')(%.+)?';
|
||||
// IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452
|
||||
// by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
|
||||
// http://intermapper.com/
|
||||
const ipv6 = '('
|
||||
+ '(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
|
||||
+ '|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
|
||||
+ '|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
|
||||
+ ')(%.+)?';
|
||||
|
||||
var ipv4 = '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.'
|
||||
+ '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.'
|
||||
+ '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.'
|
||||
+ '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])';
|
||||
const ipv4 = '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.'
|
||||
+ '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.'
|
||||
+ '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.'
|
||||
+ '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])';
|
||||
|
||||
// This would have been a lot cleaner if JS RegExp supported conditionals...
|
||||
var linkRegExpString =
|
||||
// This would have been a lot cleaner if JS RegExp supported conditionals...
|
||||
const linkRegExpString =
|
||||
|
||||
// begin match for protocol / username / password / host
|
||||
'(?:'
|
||||
// begin match for protocol / username / password / host
|
||||
'(?:'
|
||||
|
||||
// ============================
|
||||
// If we have a recognized protocol at the beginning of the URL, we're
|
||||
// more relaxed about what we accept, because we assume the user wants
|
||||
// this to be a URL, and we're not accidentally matching human language
|
||||
+ protocols + '?'
|
||||
// ============================
|
||||
// If we have a recognized protocol at the beginning of the URL, we're
|
||||
// more relaxed about what we accept, because we assume the user wants
|
||||
// this to be a URL, and we're not accidentally matching human language
|
||||
+ protocols + '?'
|
||||
|
||||
// optional username:password@
|
||||
+ credentials + '?'
|
||||
// optional username:password@
|
||||
+ credentials + '?'
|
||||
|
||||
// IP address (both v4 and v6)
|
||||
+ '(?:'
|
||||
// IP address (both v4 and v6)
|
||||
+ '(?:'
|
||||
|
||||
// IPv6
|
||||
+ ipv6
|
||||
// IPv6
|
||||
+ ipv6
|
||||
|
||||
// IPv4
|
||||
+ '|' + ipv4
|
||||
// IPv4
|
||||
+ '|' + ipv4
|
||||
|
||||
+ ')'
|
||||
+ ')'
|
||||
|
||||
// end match for protocol / username / password / host
|
||||
+ ')'
|
||||
// end match for protocol / username / password / host
|
||||
+ ')'
|
||||
|
||||
// optional port number
|
||||
+ '(?:\\:\\d{1,5})?'
|
||||
// optional port number
|
||||
+ '(?:\\:\\d{1,5})?'
|
||||
|
||||
// plus optional path and query params (no unicode allowed here?)
|
||||
+ '(?:'
|
||||
+ '\\/(?:'
|
||||
// some characters we'll accept because it's unlikely human language
|
||||
// would use them after a URL unless they were part of the url
|
||||
+ '(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])'
|
||||
+ '|(?:\\%[a-f0-9]{2})'
|
||||
// some characters are much more likely to be used AFTER a url and
|
||||
// were not intended to be included in the url itself. Mostly end
|
||||
// of sentence type things. It's also likely that the URL would
|
||||
// still work if any of these characters were missing from the end
|
||||
// because we parsed it incorrectly. For these characters to be accepted
|
||||
// they must be followed by another character that we're reasonably
|
||||
// sure is part of the url
|
||||
+ "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))"
|
||||
+ ')*'
|
||||
+ '|\\b|\$'
|
||||
+ ')';
|
||||
// plus optional path and query params (no unicode allowed here?)
|
||||
+ '(?:'
|
||||
+ '\\/(?:'
|
||||
// some characters we'll accept because it's unlikely human language
|
||||
// would use them after a URL unless they were part of the url
|
||||
+ '(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])'
|
||||
+ '|(?:\\%[a-f0-9]{2})'
|
||||
// some characters are much more likely to be used AFTER a url and
|
||||
// were not intended to be included in the url itself. Mostly end
|
||||
// of sentence type things. It's also likely that the URL would
|
||||
// still work if any of these characters were missing from the end
|
||||
// because we parsed it incorrectly. For these characters to be accepted
|
||||
// they must be followed by another character that we're reasonably
|
||||
// sure is part of the url
|
||||
+ "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))"
|
||||
+ ')*'
|
||||
+ '|\\b|\$'
|
||||
+ ')';
|
||||
|
||||
// regex = XRegExp(regex,'gi');
|
||||
var linkRegExp = RegExp(linkRegExpString, 'gi');
|
||||
// regex = XRegExp(regex,'gi');
|
||||
const linkRegExp = RegExp(linkRegExpString, 'gi');
|
||||
|
||||
var protocolRegExp = RegExp('^' + protocols, 'i');
|
||||
const protocolRegExp = RegExp('^' + protocols, 'i');
|
||||
|
||||
// if url doesn't begin with a known protocol, add http by default
|
||||
function ensureProtocol(url) {
|
||||
if (!url.match(protocolRegExp)) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
return url;
|
||||
// if url doesn't begin with a known protocol, add http by default
|
||||
function ensureProtocol(url) {
|
||||
if (!url.match(protocolRegExp)) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// look for links in the text
|
||||
var LinkParser = {
|
||||
parse: function (text) {
|
||||
var links = [];
|
||||
var match;
|
||||
// look for links in the text
|
||||
const LinkParser = {
|
||||
parse: function (text) {
|
||||
const links = [];
|
||||
let match;
|
||||
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (match = linkRegExp.exec(text)) {
|
||||
console.debug(match);
|
||||
var txt = match[0];
|
||||
var pos = match.index;
|
||||
var len = txt.length;
|
||||
var url = ensureProtocol(text);
|
||||
links.push({ 'pos': pos, 'text': txt, 'len': len, 'url': url });
|
||||
}
|
||||
|
||||
return links;
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (match = linkRegExp.exec(text)) {
|
||||
console.debug(match);
|
||||
const txt = match[0];
|
||||
const pos = match.index;
|
||||
const len = txt.length;
|
||||
const url = ensureProtocol(text);
|
||||
links.push({ 'pos': pos, 'text': txt, 'len': len, 'url': url });
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
window.LinkParser = LinkParser;
|
||||
})();
|
||||
|
||||
var cache = {};
|
||||
|
||||
function isValidIpAddress(address) {
|
||||
var links = LinkParser.parse(address);
|
||||
|
||||
return links.length == 1;
|
||||
}
|
||||
|
||||
function isLocalIpAddress(address) {
|
||||
address = address.toLowerCase();
|
||||
|
||||
if (address.indexOf('127.0.0.1') !== -1) {
|
||||
return true;
|
||||
}
|
||||
if (address.indexOf('localhost') !== -1) {
|
||||
return true;
|
||||
return links;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getServerAddress(apiClient) {
|
||||
var serverAddress = apiClient.serverAddress();
|
||||
|
||||
if (isValidIpAddress(serverAddress) && !isLocalIpAddress(serverAddress)) {
|
||||
return Promise.resolve(serverAddress);
|
||||
}
|
||||
|
||||
var cachedValue = getCachedValue(serverAddress);
|
||||
if (cachedValue) {
|
||||
return Promise.resolve(cachedValue);
|
||||
}
|
||||
|
||||
return apiClient.getEndpointInfo().then(function (endpoint) {
|
||||
if (endpoint.IsInNetwork) {
|
||||
return apiClient.getPublicSystemInfo().then(function (info) {
|
||||
var localAddress = info.LocalAddress;
|
||||
if (!localAddress) {
|
||||
console.debug('No valid local address returned, defaulting to external one');
|
||||
localAddress = serverAddress;
|
||||
}
|
||||
addToCache(serverAddress, localAddress);
|
||||
return localAddress;
|
||||
});
|
||||
} else {
|
||||
addToCache(serverAddress, serverAddress);
|
||||
return serverAddress;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearCache() {
|
||||
cache = {};
|
||||
}
|
||||
|
||||
function addToCache(key, value) {
|
||||
cache[key] = {
|
||||
value: value,
|
||||
time: new Date().getTime()
|
||||
};
|
||||
}
|
||||
|
||||
function getCachedValue(key) {
|
||||
var obj = cache[key];
|
||||
|
||||
if (obj && (new Date().getTime() - obj.time) < 180000) {
|
||||
return obj.value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
events.on(ConnectionManager, 'localusersignedin', clearCache);
|
||||
events.on(ConnectionManager, 'localusersignedout', clearCache);
|
||||
|
||||
return {
|
||||
getServerAddress: getServerAddress
|
||||
};
|
||||
});
|
||||
|
||||
window.LinkParser = LinkParser;
|
||||
})();
|
||||
|
||||
let cache = {};
|
||||
|
||||
// TODO: Replace with isIP (https://www.npmjs.com/package/is-ip)
|
||||
function isValidIpAddress(address) {
|
||||
const links = LinkParser.parse(address);
|
||||
|
||||
return links.length == 1;
|
||||
}
|
||||
|
||||
// TODO: Add IPv6 support. Potentially replace with isLocalhost (https://www.npmjs.com/package/is-localhost-ip)
|
||||
function isLocalIpAddress(address) {
|
||||
address = address.toLowerCase();
|
||||
|
||||
if (address.includes('127.0.0.1')) {
|
||||
return true;
|
||||
}
|
||||
if (address.includes('localhost')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getServerAddress(apiClient) {
|
||||
const serverAddress = apiClient.serverAddress();
|
||||
|
||||
if (isValidIpAddress(serverAddress) && !isLocalIpAddress(serverAddress)) {
|
||||
return Promise.resolve(serverAddress);
|
||||
}
|
||||
|
||||
const cachedValue = getCachedValue(serverAddress);
|
||||
if (cachedValue) {
|
||||
return Promise.resolve(cachedValue);
|
||||
}
|
||||
|
||||
return apiClient.getEndpointInfo().then(function (endpoint) {
|
||||
if (endpoint.IsInNetwork) {
|
||||
return apiClient.getPublicSystemInfo().then(function (info) {
|
||||
let localAddress = info.LocalAddress;
|
||||
if (!localAddress) {
|
||||
console.debug('No valid local address returned, defaulting to external one');
|
||||
localAddress = serverAddress;
|
||||
}
|
||||
addToCache(serverAddress, localAddress);
|
||||
return localAddress;
|
||||
});
|
||||
} else {
|
||||
addToCache(serverAddress, serverAddress);
|
||||
return serverAddress;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearCache() {
|
||||
cache = {};
|
||||
}
|
||||
|
||||
function addToCache(key, value) {
|
||||
cache[key] = {
|
||||
value: value,
|
||||
time: new Date().getTime()
|
||||
};
|
||||
}
|
||||
|
||||
function getCachedValue(key) {
|
||||
const obj = cache[key];
|
||||
|
||||
if (obj && (new Date().getTime() - obj.time) < 180000) {
|
||||
return obj.value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
events.on(window.connectionManager, 'localusersignedin', clearCache);
|
||||
events.on(window.connectionManager, 'localusersignedout', clearCache);
|
||||
|
||||
export default {
|
||||
getServerAddress: getServerAddress
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import appSettings from 'appSettings';
|
||||
import * as userSettings from 'userSettings';
|
||||
import playbackManager from 'playbackManager';
|
||||
import connectionManager from 'connectionManager';
|
||||
import globalize from 'globalize';
|
||||
import events from 'events';
|
||||
import castSenderApiLoader from 'castSenderApiLoader';
|
||||
|
@ -325,11 +324,11 @@ class CastPlayer {
|
|||
|
||||
let apiClient;
|
||||
if (message.options && message.options.ServerId) {
|
||||
apiClient = connectionManager.getApiClient(message.options.ServerId);
|
||||
apiClient = window.connectionManager.getApiClient(message.options.ServerId);
|
||||
} else if (message.options && message.options.items && message.options.items.length) {
|
||||
apiClient = connectionManager.getApiClient(message.options.items[0].ServerId);
|
||||
apiClient = window.connectionManager.getApiClient(message.options.items[0].ServerId);
|
||||
} else {
|
||||
apiClient = connectionManager.currentApiClient();
|
||||
apiClient = window.connectionManager.currentApiClient();
|
||||
}
|
||||
|
||||
message = Object.assign(message, {
|
||||
|
@ -673,7 +672,7 @@ class ChromecastPlayer {
|
|||
|
||||
playWithCommand(options, command) {
|
||||
if (!options.items) {
|
||||
const apiClient = connectionManager.getApiClient(options.serverId);
|
||||
const apiClient = window.connectionManager.getApiClient(options.serverId);
|
||||
const instance = this;
|
||||
|
||||
return apiClient.getItem(apiClient.getCurrentUserId(), options.ids[0]).then(function (item) {
|
||||
|
@ -931,9 +930,9 @@ class ChromecastPlayer {
|
|||
return state.VolumeLevel == null ? 100 : state.VolumeLevel;
|
||||
}
|
||||
|
||||
isPlaying() {
|
||||
isPlaying(mediaType) {
|
||||
const state = this.lastPlayerData || {};
|
||||
return state.NowPlayingItem != null;
|
||||
return state.NowPlayingItem != null && (state.NowPlayingItem.MediaType === mediaType || !mediaType);
|
||||
}
|
||||
|
||||
isPlayingVideo() {
|
||||
|
@ -950,12 +949,12 @@ class ChromecastPlayer {
|
|||
|
||||
currentTime(val) {
|
||||
if (val != null) {
|
||||
return this.seek(val);
|
||||
return this.seek(val * 10000);
|
||||
}
|
||||
|
||||
let state = this.lastPlayerData || {};
|
||||
state = state.PlayState || {};
|
||||
return state.PositionTicks;
|
||||
return state.PositionTicks / 10000;
|
||||
}
|
||||
|
||||
duration() {
|
||||
|
@ -985,7 +984,7 @@ class ChromecastPlayer {
|
|||
}
|
||||
|
||||
shuffle(item) {
|
||||
const apiClient = connectionManager.getApiClient(item.ServerId);
|
||||
const apiClient = window.connectionManager.getApiClient(item.ServerId);
|
||||
const userId = apiClient.getCurrentUserId();
|
||||
|
||||
const instance = this;
|
||||
|
@ -998,7 +997,7 @@ class ChromecastPlayer {
|
|||
}
|
||||
|
||||
instantMix(item) {
|
||||
const apiClient = connectionManager.getApiClient(item.ServerId);
|
||||
const apiClient = window.connectionManager.getApiClient(item.ServerId);
|
||||
const userId = apiClient.getCurrentUserId();
|
||||
|
||||
const instance = this;
|
||||
|
@ -1036,7 +1035,7 @@ class ChromecastPlayer {
|
|||
}
|
||||
|
||||
const instance = this;
|
||||
const apiClient = connectionManager.getApiClient(options.serverId);
|
||||
const apiClient = window.connectionManager.getApiClient(options.serverId);
|
||||
|
||||
return getItemsForPlayback(apiClient, {
|
||||
Ids: options.ids.join(',')
|
||||
|
|
213
src/plugins/comicsPlayer/plugin.js
Normal file
213
src/plugins/comicsPlayer/plugin.js
Normal file
|
@ -0,0 +1,213 @@
|
|||
import loading from 'loading';
|
||||
import dialogHelper from 'dialogHelper';
|
||||
import keyboardnavigation from 'keyboardnavigation';
|
||||
import appRouter from 'appRouter';
|
||||
import * as libarchive from 'libarchive';
|
||||
|
||||
export class ComicsPlayer {
|
||||
constructor() {
|
||||
this.name = 'Comics Player';
|
||||
this.type = 'mediaplayer';
|
||||
this.id = 'comicsplayer';
|
||||
this.priority = 1;
|
||||
this.imageMap = new Map();
|
||||
|
||||
this.onDialogClosed = this.onDialogClosed.bind(this);
|
||||
this.onWindowKeyUp = this.onWindowKeyUp.bind(this);
|
||||
}
|
||||
|
||||
play(options) {
|
||||
this.progress = 0;
|
||||
|
||||
let elem = this.createMediaElement();
|
||||
return this.setCurrentSrc(elem, options);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.unbindEvents();
|
||||
|
||||
let elem = this.mediaElement;
|
||||
if (elem) {
|
||||
dialogHelper.close(elem);
|
||||
this.mediaElement = null;
|
||||
}
|
||||
|
||||
loading.hide();
|
||||
}
|
||||
|
||||
onDialogClosed() {
|
||||
this.stop();
|
||||
}
|
||||
|
||||
onWindowKeyUp(e) {
|
||||
let key = keyboardnavigation.getKeyName(e);
|
||||
switch (key) {
|
||||
case 'Escape':
|
||||
this.stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
document.addEventListener('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
|
||||
unbindEvents() {
|
||||
document.removeEventListener('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
|
||||
createMediaElement() {
|
||||
let elem = this.mediaElement;
|
||||
if (elem) {
|
||||
return elem;
|
||||
}
|
||||
|
||||
elem = document.getElementById('comicsPlayer');
|
||||
if (!elem) {
|
||||
elem = dialogHelper.createDialog({
|
||||
exitAnimationDuration: 400,
|
||||
size: 'fullscreen',
|
||||
autoFocus: false,
|
||||
scrollY: false,
|
||||
exitAnimation: 'fadeout',
|
||||
removeOnClose: true
|
||||
});
|
||||
|
||||
elem.id = 'comicsPlayer';
|
||||
elem.classList.add('slideshowDialog');
|
||||
|
||||
elem.innerHTML = '<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>';
|
||||
|
||||
this.bindEvents();
|
||||
dialogHelper.open(elem);
|
||||
}
|
||||
|
||||
this.mediaElement = elem;
|
||||
return elem;
|
||||
}
|
||||
|
||||
setCurrentSrc(elem, options) {
|
||||
let item = options.items[0];
|
||||
this.currentItem = item;
|
||||
|
||||
loading.show();
|
||||
|
||||
let serverId = item.ServerId;
|
||||
let apiClient = window.connectionManager.getApiClient(serverId);
|
||||
|
||||
libarchive.Archive.init({
|
||||
workerUrl: appRouter.baseUrl() + '/libraries/worker-bundle.js'
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let downloadUrl = apiClient.getItemDownloadUrl(item.Id);
|
||||
const archiveSource = new ArchiveSource(downloadUrl);
|
||||
|
||||
var instance = this;
|
||||
import('swiper').then(({default: Swiper}) => {
|
||||
archiveSource.load().then(() => {
|
||||
loading.hide();
|
||||
this.swiperInstance = new Swiper(elem.querySelector('.slideshowSwiperContainer'), {
|
||||
direction: 'horizontal',
|
||||
// loop is disabled due to the lack of support in virtual slides
|
||||
loop: false,
|
||||
zoom: {
|
||||
minRatio: 1,
|
||||
toggle: true,
|
||||
containerClass: 'slider-zoom-container'
|
||||
},
|
||||
autoplay: false,
|
||||
keyboard: {
|
||||
enabled: true
|
||||
},
|
||||
preloadImages: true,
|
||||
slidesPerView: 1,
|
||||
slidesPerColumn: 1,
|
||||
initialSlide: 0,
|
||||
// reduces memory consumption for large libraries while allowing preloading of images
|
||||
virtual: {
|
||||
slides: archiveSource.urls,
|
||||
cache: true,
|
||||
renderSlide: instance.getImgFromUrl,
|
||||
addSlidesBefore: 1,
|
||||
addSlidesAfter: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getImgFromUrl(url) {
|
||||
return `<div class="swiper-slide">
|
||||
<div class="slider-zoom-container">
|
||||
<img src="${url}" class="swiper-slide-img">
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
canPlayMediaType(mediaType) {
|
||||
return (mediaType || '').toLowerCase() === 'book';
|
||||
}
|
||||
|
||||
canPlayItem(item) {
|
||||
if (item.Path && (item.Path.endsWith('cbz') || item.Path.endsWith('cbr'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class ArchiveSource {
|
||||
constructor(url) {
|
||||
this.url = url;
|
||||
this.files = [];
|
||||
this.urls = [];
|
||||
this.loadPromise = this.load();
|
||||
this.itemsLoaded = 0;
|
||||
}
|
||||
|
||||
async load() {
|
||||
let res = await fetch(this.url);
|
||||
if (!res.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
let blob = await res.blob();
|
||||
this.archive = await libarchive.Archive.open(blob);
|
||||
this.raw = await this.archive.getFilesArray();
|
||||
this.numberOfFiles = this.raw.length;
|
||||
await this.archive.extractFiles();
|
||||
|
||||
let files = await this.archive.getFilesArray();
|
||||
files.sort((a, b) => {
|
||||
if (a.file.name < b.file.name) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
for (let file of files) {
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
let url = URL.createObjectURL(file.file);
|
||||
this.urls.push(url);
|
||||
}
|
||||
}
|
||||
|
||||
getLength() {
|
||||
return this.raw.length;
|
||||
}
|
||||
|
||||
async item(index) {
|
||||
if (this.urls[index]) {
|
||||
return this.urls[index];
|
||||
}
|
||||
|
||||
await this.loadPromise;
|
||||
return this.urls[index];
|
||||
}
|
||||
}
|
||||
|
||||
export default ComicsPlayer;
|
|
@ -132,10 +132,7 @@ class HtmlAudioPlayer {
|
|||
return new Promise(function (resolve, reject) {
|
||||
requireHlsPlayer(function () {
|
||||
const hls = new Hls({
|
||||
manifestLoadingTimeOut: 20000,
|
||||
xhrSetup: function (xhr, url) {
|
||||
xhr.withCredentials = true;
|
||||
}
|
||||
manifestLoadingTimeOut: 20000
|
||||
});
|
||||
hls.loadSource(val);
|
||||
hls.attachMedia(elem);
|
||||
|
|
|
@ -5,7 +5,6 @@ import loading from 'loading';
|
|||
import dom from 'dom';
|
||||
import playbackManager from 'playbackManager';
|
||||
import appRouter from 'appRouter';
|
||||
import connectionManager from 'connectionManager';
|
||||
import {
|
||||
bindEventsToHlsPlayer,
|
||||
destroyHlsPlayer,
|
||||
|
@ -325,7 +324,7 @@ function tryRemoveElement(elem) {
|
|||
|
||||
console.debug(`prefetching hls playlist: ${hlsPlaylistUrl}`);
|
||||
|
||||
return connectionManager.getApiClient(item.ServerId).ajax({
|
||||
return window.connectionManager.getApiClient(item.ServerId).ajax({
|
||||
|
||||
type: 'GET',
|
||||
url: hlsPlaylistUrl
|
||||
|
@ -393,10 +392,7 @@ function tryRemoveElement(elem) {
|
|||
return new Promise((resolve, reject) => {
|
||||
requireHlsPlayer(() => {
|
||||
const hls = new Hls({
|
||||
manifestLoadingTimeOut: 20000,
|
||||
xhrSetup(xhr) {
|
||||
xhr.withCredentials = true;
|
||||
}
|
||||
manifestLoadingTimeOut: 20000
|
||||
});
|
||||
hls.loadSource(url);
|
||||
hls.attachMedia(elem);
|
||||
|
@ -1041,7 +1037,7 @@ function tryRemoveElement(elem) {
|
|||
// embedded font url
|
||||
return avaliableFonts.push(i.DeliveryUrl);
|
||||
});
|
||||
const apiClient = connectionManager.getApiClient(item);
|
||||
const apiClient = window.connectionManager.getApiClient(item);
|
||||
const fallbackFontList = apiClient.getUrl('/FallbackFont/Fonts', {
|
||||
api_key: apiClient.accessToken()
|
||||
});
|
||||
|
@ -1413,7 +1409,12 @@ function tryRemoveElement(elem) {
|
|||
const list = [];
|
||||
|
||||
const video = document.createElement('video');
|
||||
if (video.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === 'function' || document.pictureInPictureEnabled) {
|
||||
if (
|
||||
// Check non-standard Safari PiP support
|
||||
typeof video.webkitSupportsPresentationMode === 'function' && video.webkitSupportsPresentationMode('picture-in-picture') && typeof video.webkitSetPresentationMode === 'function'
|
||||
// Check standard PiP support
|
||||
|| document.pictureInPictureEnabled
|
||||
) {
|
||||
list.push('PictureInPicture');
|
||||
} else if (window.Windows) {
|
||||
if (Windows.UI.ViewManagement.ApplicationView.getForCurrentView().isViewModeSupported(Windows.UI.ViewManagement.ApplicationViewMode.compactOverlay)) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import connectionManager from 'connectionManager';
|
||||
|
||||
export default class PhotoPlayer {
|
||||
constructor() {
|
||||
|
@ -13,7 +12,7 @@ export default class PhotoPlayer {
|
|||
import('slideshow').then(({default: slideshow}) => {
|
||||
var index = options.startIndex || 0;
|
||||
|
||||
var apiClient = connectionManager.currentApiClient();
|
||||
var apiClient = window.connectionManager.currentApiClient();
|
||||
apiClient.getCurrentUser().then(function(result) {
|
||||
var newSlideShow = new slideshow({
|
||||
showTitle: false,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import connectionManager from 'connectionManager';
|
||||
import globalize from 'globalize';
|
||||
|
||||
function showErrorMessage() {
|
||||
|
@ -25,7 +24,7 @@ class PlayAccessValidation {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return connectionManager.getApiClient(serverId).getCurrentUser().then(function (user) {
|
||||
return window.connectionManager.getApiClient(serverId).getCurrentUser().then(function (user) {
|
||||
if (user.Policy.EnableMediaPlayback) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import playbackManager from 'playbackManager';
|
||||
import events from 'events';
|
||||
import serverNotifications from 'serverNotifications';
|
||||
import connectionManager from 'connectionManager';
|
||||
|
||||
function getActivePlayerId() {
|
||||
const info = playbackManager.getPlayerInfo();
|
||||
|
@ -54,10 +53,10 @@ function getCurrentApiClient(instance) {
|
|||
const currentServerId = instance.currentServerId;
|
||||
|
||||
if (currentServerId) {
|
||||
return connectionManager.getApiClient(currentServerId);
|
||||
return window.connectionManager.getApiClient(currentServerId);
|
||||
}
|
||||
|
||||
return connectionManager.currentApiClient();
|
||||
return window.connectionManager.currentApiClient();
|
||||
}
|
||||
|
||||
function sendCommandByName(instance, name, options) {
|
||||
|
@ -322,12 +321,12 @@ class SessionPlayer {
|
|||
|
||||
currentTime(val) {
|
||||
if (val != null) {
|
||||
return this.seek(val);
|
||||
return this.seek(val * 10000);
|
||||
}
|
||||
|
||||
let state = this.lastPlayerData || {};
|
||||
state = state.PlayState || {};
|
||||
return state.PositionTicks;
|
||||
return state.PositionTicks / 10000;
|
||||
}
|
||||
|
||||
duration() {
|
||||
|
@ -466,9 +465,9 @@ class SessionPlayer {
|
|||
sendCommandByName(this, 'DisplayContent', options);
|
||||
}
|
||||
|
||||
isPlaying() {
|
||||
isPlaying(mediaType) {
|
||||
const state = this.lastPlayerData || {};
|
||||
return state.NowPlayingItem != null;
|
||||
return state.NowPlayingItem != null && (state.NowPlayingItem.MediaType === mediaType || !mediaType);
|
||||
}
|
||||
|
||||
isPlayingVideo() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue