1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Add barebones comic book reader

This commit is contained in:
MrTimscampi 2020-05-29 23:32:45 +02:00
parent aae276dad1
commit 2ea2132740
8 changed files with 252 additions and 6 deletions

View file

@ -69,6 +69,7 @@
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
"jquery": "^3.5.1", "jquery": "^3.5.1",
"jstree": "^3.3.7", "jstree": "^3.3.7",
"libarchive.js": "^1.3.0",
"libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv", "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv",
"material-design-icons-iconfont": "^5.0.1", "material-design-icons-iconfont": "^5.0.1",
"native-promise-only": "^0.8.0-a", "native-promise-only": "^0.8.0-a",
@ -92,6 +93,7 @@
"src/components/autoFocuser.js", "src/components/autoFocuser.js",
"src/components/cardbuilder/cardBuilder.js", "src/components/cardbuilder/cardBuilder.js",
"src/scripts/fileDownloader.js", "src/scripts/fileDownloader.js",
"src/components/comicsPlayer/plugin.js",
"src/components/images/imageLoader.js", "src/components/images/imageLoader.js",
"src/components/lazyLoader/lazyLoaderIntersectionObserver.js", "src/components/lazyLoader/lazyLoaderIntersectionObserver.js",
"src/components/playback/mediasession.js", "src/components/playback/mediasession.js",

View file

@ -176,3 +176,9 @@ _define('connectionManagerFactory', function () {
_define('appStorage', function () { _define('appStorage', function () {
return apiclient.AppStorage; return apiclient.AppStorage;
}); });
// libarchive.js
var libarchive = require('libarchive.js');
_define('libarchive', function () {
return libarchive;
});

View file

@ -0,0 +1,215 @@
import connectionManager from 'connectionManager';
import loading from 'loading';
import dialogHelper from 'dialogHelper';
import keyboardnavigation from 'keyboardnavigation';
import appRouter from 'appRouter';
import 'css!../slideshow/style';
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;
}
// Hide loader in case player was not fully loaded yet
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 = 'bookPlayer';
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 = 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 virtual slides option not supporting it.
loop: false,
zoom: {
minRatio: 1,
toggle: true,
containerClass: 'slider-zoom-container'
},
autoplay: false,
keyboard: {
enabled: true
},
preloadImages: true,
slidesPerView: 1,
slidesPerColumn: 1,
initialSlide: 0,
// Virtual slides reduce 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) {
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;

View file

@ -2187,7 +2187,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
// Only used internally // Only used internally
self.getCurrentTicks = getCurrentTicks; self.getCurrentTicks = getCurrentTicks;
function playPhotos(items, options, user) { function playOther(items, options, user) {
var playStartIndex = options.startIndex || 0; var playStartIndex = options.startIndex || 0;
var player = getPlayer(items[playStartIndex], options); var player = getPlayer(items[playStartIndex], options);
@ -2216,9 +2216,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
return Promise.reject(); return Promise.reject();
} }
if (firstItem.MediaType === 'Photo') { if (firstItem.MediaType === 'Photo' || firstItem.MediaType === 'Book') {
return playPhotos(items, options, user); return playOther(items, options, user);
} }
var apiClient = connectionManager.getApiClient(firstItem.ServerId); var apiClient = connectionManager.getApiClient(firstItem.ServerId);

View file

@ -58,7 +58,7 @@ define(['events', 'globalize'], function (events, globalize) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
require([pluginSpec], (pluginFactory) => { require([pluginSpec], (pluginFactory) => {
var plugin = new pluginFactory(); var plugin = pluginFactory.default ? new pluginFactory.default() : new pluginFactory();
// See if it's already installed // See if it's already installed
var existing = instance.pluginsList.filter(function (p) { var existing = instance.pluginsList.filter(function (p) {

View file

@ -490,6 +490,7 @@ var AppInfo = {};
'components/playback/experimentalwarnings', 'components/playback/experimentalwarnings',
'components/htmlAudioPlayer/plugin', 'components/htmlAudioPlayer/plugin',
'components/htmlVideoPlayer/plugin', 'components/htmlVideoPlayer/plugin',
'components/comicsPlayer/plugin',
'components/photoPlayer/plugin', 'components/photoPlayer/plugin',
'components/youtubeplayer/plugin', 'components/youtubeplayer/plugin',
'components/backdropScreensaver/plugin', 'components/backdropScreensaver/plugin',
@ -701,7 +702,8 @@ var AppInfo = {};
'events', 'events',
'credentialprovider', 'credentialprovider',
'connectionManagerFactory', 'connectionManagerFactory',
'appStorage' 'appStorage',
'comicReader'
] ]
}, },
urlArgs: urlArgs, urlArgs: urlArgs,

View file

@ -1,10 +1,12 @@
const path = require('path'); const path = require('path');
const CopyPlugin = require('copy-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin');
const WorkerPlugin = require('worker-plugin');
const Assets = [ const Assets = [
'alameda/alameda.js', 'alameda/alameda.js',
'native-promise-only/npo.js', 'native-promise-only/npo.js',
'libarchive.js/dist/worker-bundle.js',
'libass-wasm/dist/js/subtitles-octopus-worker.js', 'libass-wasm/dist/js/subtitles-octopus-worker.js',
'libass-wasm/dist/js/subtitles-octopus-worker.data', 'libass-wasm/dist/js/subtitles-octopus-worker.data',
'libass-wasm/dist/js/subtitles-octopus-worker.wasm', 'libass-wasm/dist/js/subtitles-octopus-worker.wasm',
@ -13,6 +15,11 @@ const Assets = [
'libass-wasm/dist/js/subtitles-octopus-worker-legacy.js.mem' 'libass-wasm/dist/js/subtitles-octopus-worker-legacy.js.mem'
]; ];
const LibarchiveWasm = [
'libarchive.js/dist/wasm-gen/libarchive.js',
'libarchive.js/dist/wasm-gen/libarchive.wasm'
];
module.exports = { module.exports = {
context: path.resolve(__dirname, 'src'), context: path.resolve(__dirname, 'src'),
entry: './bundle.js', entry: './bundle.js',
@ -34,6 +41,15 @@ module.exports = {
to: path.resolve(__dirname, './dist/libraries') to: path.resolve(__dirname, './dist/libraries')
}; };
}) })
) ),
new CopyPlugin(
LibarchiveWasm.map(asset => {
return {
from: path.resolve(__dirname, `./node_modules/${asset}`),
to: path.resolve(__dirname, './dist/libraries/wasm-gen/')
};
})
),
new WorkerPlugin()
] ]
}; };

View file

@ -6783,6 +6783,11 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2" prelude-ls "~1.1.2"
type-check "~0.3.2" type-check "~0.3.2"
libarchive.js@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/libarchive.js/-/libarchive.js-1.3.0.tgz#18c42c6b4ce727a02359c90769e4e454cf3743cd"
integrity sha512-EkQfRXt9DhWwj6BnEA2TNpOf4jTnzSTUPGgE+iFxcdNqjktY8GitbDeHnx8qZA0/IukNyyBUR3oQKRdYkO+HFg==
"libass-wasm@https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv": "libass-wasm@https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv":
version "4.0.0" version "4.0.0"
resolved "https://github.com/jellyfin/JavascriptSubtitlesOctopus#58e9a3f1a7f7883556ee002545f445a430120639" resolved "https://github.com/jellyfin/JavascriptSubtitlesOctopus#58e9a3f1a7f7883556ee002545f445a430120639"