Merge pull request #1325 from MrTimscampi/comic-reader
Add barebones comic book reader
This commit is contained in:
commit
10cbb9f198
7 changed files with 256 additions and 3 deletions
|
@ -52,7 +52,8 @@
|
|||
"stylelint-order": "^4.1.0",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-merge": "^4.2.2",
|
||||
"webpack-stream": "^6.0.0"
|
||||
"webpack-stream": "^6.0.0",
|
||||
"worker-plugin": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"alameda": "^1.4.0",
|
||||
|
@ -71,6 +72,7 @@
|
|||
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
|
||||
"jquery": "^3.5.1",
|
||||
"jstree": "^3.3.10",
|
||||
"libarchive.js": "^1.3.0",
|
||||
"libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv",
|
||||
"material-design-icons-iconfont": "^5.0.1",
|
||||
"native-promise-only": "^0.8.0-a",
|
||||
|
@ -178,6 +180,7 @@
|
|||
"src/plugins/experimentalWarnings/plugin.js",
|
||||
"src/plugins/sessionPlayer/plugin.js",
|
||||
"src/plugins/htmlAudioPlayer/plugin.js",
|
||||
"src/plugins/comicsPlayer/plugin.js",
|
||||
"src/plugins/chromecastPlayer/plugin.js",
|
||||
"src/components/slideshow/slideshow.js",
|
||||
"src/components/sortmenu/sortmenu.js",
|
||||
|
|
|
@ -175,3 +175,9 @@ _define('connectionManagerFactory', function () {
|
|||
_define('appStorage', function () {
|
||||
return apiclient.AppStorage;
|
||||
});
|
||||
|
||||
// libarchive.js
|
||||
var libarchive = require('libarchive.js');
|
||||
_define('libarchive', function () {
|
||||
return libarchive;
|
||||
});
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"plugins/htmlAudioPlayer/plugin",
|
||||
"plugins/htmlVideoPlayer/plugin",
|
||||
"plugins/photoPlayer/plugin",
|
||||
"plugins/comicsPlayer/plugin",
|
||||
"plugins/bookPlayer/plugin",
|
||||
"plugins/youtubePlayer/plugin",
|
||||
"plugins/backdropScreensaver/plugin",
|
||||
|
|
214
src/plugins/comicsPlayer/plugin.js
Normal file
214
src/plugins/comicsPlayer/plugin.js
Normal file
|
@ -0,0 +1,214 @@
|
|||
import connectionManager from 'connectionManager';
|
||||
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 = 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;
|
|
@ -526,7 +526,8 @@ function initClient() {
|
|||
'events',
|
||||
'credentialprovider',
|
||||
'connectionManagerFactory',
|
||||
'appStorage'
|
||||
'appStorage',
|
||||
'comicReader'
|
||||
]
|
||||
},
|
||||
urlArgs: urlArgs,
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
const path = require('path');
|
||||
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const WorkerPlugin = require('worker-plugin');
|
||||
|
||||
const Assets = [
|
||||
'alameda/alameda.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.data',
|
||||
'libass-wasm/dist/js/subtitles-octopus-worker.wasm',
|
||||
|
@ -13,6 +15,11 @@ const Assets = [
|
|||
'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 = {
|
||||
context: path.resolve(__dirname, 'src'),
|
||||
entry: './bundle.js',
|
||||
|
@ -35,6 +42,15 @@ module.exports = {
|
|||
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()
|
||||
]
|
||||
};
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -6560,6 +6560,11 @@ levn@^0.4.1:
|
|||
prelude-ls "^1.2.1"
|
||||
type-check "~0.4.0"
|
||||
|
||||
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":
|
||||
version "4.0.0"
|
||||
resolved "https://github.com/jellyfin/JavascriptSubtitlesOctopus#58e9a3f1a7f7883556ee002545f445a430120639"
|
||||
|
@ -12036,6 +12041,13 @@ worker-farm@^1.7.0:
|
|||
dependencies:
|
||||
errno "~0.1.7"
|
||||
|
||||
worker-plugin@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/worker-plugin/-/worker-plugin-5.0.0.tgz#113b5fe1f4a5d6a957cecd29915bedafd70bb537"
|
||||
integrity sha512-AXMUstURCxDD6yGam2r4E34aJg6kW85IiaeX72hi+I1cxyaMUtrvVY6sbfpGKAj5e7f68Acl62BjQF5aOOx2IQ==
|
||||
dependencies:
|
||||
loader-utils "^1.1.0"
|
||||
|
||||
wrap-ansi@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue