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

320 lines
9.8 KiB
JavaScript
Raw Normal View History

2020-08-14 08:46:34 +02:00
import loading from '../../components/loading/loading';
2020-08-16 20:24:45 +02:00
import keyboardnavigation from '../../scripts/keyboardNavigation';
import dialogHelper from '../../components/dialogHelper/dialogHelper';
import dom from '../../scripts/dom';
2020-09-08 02:05:02 -04:00
import { ConnectionManager, Events } from 'jellyfin-apiclient';
2020-08-16 20:24:45 +02:00
import './style.css';
2020-08-14 08:46:34 +02:00
import 'material-design-icons-iconfont';
import '../../elements/emby-button/paper-icon-button-light';
2020-06-06 15:53:47 +09:00
import TableOfContents from './tableOfContents';
export class BookPlayer {
constructor() {
this.name = 'Book Player';
this.type = 'mediaplayer';
this.id = 'bookplayer';
this.priority = 1;
this.onDialogClosed = this.onDialogClosed.bind(this);
this.openTableOfContents = this.openTableOfContents.bind(this);
this.prevChapter = this.prevChapter.bind(this);
this.nextChapter = this.nextChapter.bind(this);
this.onWindowKeyUp = this.onWindowKeyUp.bind(this);
}
2020-05-16 22:02:52 +10:00
play(options) {
2020-09-01 22:55:08 +09:00
this.progress = 0;
this.cancellationToken = false;
this.loaded = false;
loading.show();
const elem = this.createMediaElement();
return this.setCurrentSrc(elem, options);
}
2020-05-16 22:02:52 +10:00
stop() {
this.unbindEvents();
2020-09-01 22:55:08 +09:00
const elem = this.mediaElement;
const tocElement = this.tocElement;
const rendition = this.rendition;
2020-05-16 22:02:52 +10:00
if (elem) {
dialogHelper.close(elem);
2020-09-01 22:55:08 +09:00
this.mediaElement = null;
}
if (tocElement) {
tocElement.destroy();
2020-09-01 22:55:08 +09:00
this.tocElement = null;
}
if (rendition) {
rendition.destroy();
}
2020-09-01 22:55:08 +09:00
// hide loader in case player was not fully loaded yet
loading.hide();
2020-09-01 22:55:08 +09:00
this.cancellationToken = true;
}
currentItem() {
2020-09-01 22:55:08 +09:00
return this.item;
}
currentTime() {
2020-09-01 22:55:08 +09:00
return this.progress * 1000;
}
duration() {
return 1000;
}
getBufferedRanges() {
return [{
start: 0,
end: 10000000
}];
}
volume() {
return 100;
}
isMuted() {
return false;
}
paused() {
return false;
}
seekable() {
return true;
}
2020-05-16 22:02:52 +10:00
onWindowKeyUp(e) {
const key = keyboardnavigation.getKeyName(e);
2020-09-01 22:55:08 +09:00
const rendition = this.rendition;
const book = rendition.book;
2020-09-01 22:55:08 +09:00
if (!this.loaded) return;
switch (key) {
case 'l':
case 'ArrowRight':
case 'Right':
book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next();
break;
case 'j':
case 'ArrowLeft':
case 'Left':
book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev();
break;
case 'Escape':
2020-09-01 22:55:08 +09:00
if (this.tocElement) {
// Close table of contents on ESC if it is open
2020-09-01 22:55:08 +09:00
this.tocElement.destroy();
} else {
// Otherwise stop the entire book player
this.stop();
}
break;
2020-05-16 22:02:52 +10:00
}
}
2020-05-16 22:02:52 +10:00
onDialogClosed() {
this.stop();
}
2020-05-16 22:02:52 +10:00
bindMediaElementEvents() {
2020-09-01 22:55:08 +09:00
const elem = this.mediaElement;
2020-05-19 16:05:44 +10:00
elem.addEventListener('close', this.onDialogClosed, {once: true});
elem.querySelector('#btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true});
elem.querySelector('#btnBookplayerToc').addEventListener('click', this.openTableOfContents);
if (browser.mobile) {
elem.querySelector('#btnBookplayerPrev').addEventListener('click', this.prevChapter);
elem.querySelector('#btnBookplayerNext').addEventListener('click', this.nextChapter);
}
}
2020-05-19 16:05:44 +10:00
bindEvents() {
this.bindMediaElementEvents();
document.addEventListener('keyup', this.onWindowKeyUp);
// FIXME: I don't really get why document keyup event is not triggered when epub is in focus
2020-09-01 22:55:08 +09:00
this.rendition.on('keyup', this.onWindowKeyUp);
}
unbindMediaElementEvents() {
2020-09-01 22:55:08 +09:00
const elem = this.mediaElement;
elem.removeEventListener('close', this.onDialogClosed);
elem.querySelector('#btnBookplayerExit').removeEventListener('click', this.onDialogClosed);
elem.querySelector('#btnBookplayerToc').removeEventListener('click', this.openTableOfContents);
if (browser.mobile) {
elem.querySelector('#btnBookplayerPrev').removeEventListener('click', this.prevChapter);
elem.querySelector('#btnBookplayerNext').removeEventListener('click', this.nextChapter);
}
}
unbindEvents() {
2020-09-01 22:55:08 +09:00
if (this.mediaElement) {
this.unbindMediaElementEvents();
}
document.removeEventListener('keyup', this.onWindowKeyUp);
2020-09-01 22:55:08 +09:00
if (this.rendition) {
this.rendition.off('keyup', this.onWindowKeyUp);
}
}
openTableOfContents() {
2020-09-01 22:55:08 +09:00
if (this.loaded) {
this.tocElement = new TableOfContents(this);
}
}
prevChapter(e) {
this._rendition.prev();
e.preventDefault();
}
nextChapter(e) {
this._rendition.next();
e.preventDefault();
}
createMediaElement() {
2020-09-01 22:55:08 +09:00
let elem = this.mediaElement;
if (elem) {
return elem;
2020-05-19 16:05:44 +10:00
}
elem = document.getElementById('bookPlayer');
if (!elem) {
elem = dialogHelper.createDialog({
exitAnimationDuration: 400,
size: 'fullscreen',
autoFocus: false,
scrollY: false,
exitAnimation: 'fadeout',
removeOnClose: true
});
let html = '';
if (browser.mobile) {
html += '<div class="button-wrapper top-button"><button id="btnBookplayerPrev" is="paper-icon-button-light" class="autoSize bookplayerButton hide-mouse-idle-tv"><i class="material-icons bookplayerButtonIcon navigate_before"></i> Prev</button></div>';
}
html += '<div id="viewer">';
html += '<div class="topButtons">';
html += '<button is="paper-icon-button-light" id="btnBookplayerToc" class="autoSize bookplayerButton hide-mouse-idle-tv" tabindex="-1"><i class="material-icons bookplayerButtonIcon toc"></i></button>';
html += '<button is="paper-icon-button-light" id="btnBookplayerExit" class="autoSize bookplayerButton hide-mouse-idle-tv" tabindex="-1"><i class="material-icons bookplayerButtonIcon close"></i></button>';
html += '</div>';
html += '</div>';
2020-05-16 22:02:52 +10:00
if (browser.mobile) {
html += '<div class="button-wrapper bottom-button"><button id="btnBookplayerNext" is="paper-icon-button-light" class="autoSize bookplayerButton hide-mouse-idle-tv">Next <i class="material-icons bookplayerButtonIcon navigate_next"></i></button></div>';
}
2020-09-01 22:55:08 +09:00
elem.id = 'bookPlayer';
elem.innerHTML = html;
2020-05-16 22:02:52 +10:00
dialogHelper.open(elem);
2020-05-16 22:02:52 +10:00
}
2020-09-01 22:55:08 +09:00
this.mediaElement = elem;
return elem;
}
2020-05-16 22:02:52 +10:00
render(elem, book) {
if (browser.mobile) {
return book.renderTo(elem, {
width: '100%',
height: '100%',
flow: 'scrolled-doc'
});
} else {
return book.renderTo(elem, {
width: '100%',
height: '100%'
});
}
}
setCurrentSrc(elem, options) {
const item = options.items[0];
2020-09-01 22:55:08 +09:00
this.item = item;
this.streamInfo = {
started: true,
ended: false,
mediaSource: {
Id: item.Id
}
};
const serverId = item.ServerId;
2020-08-16 20:24:45 +02:00
const apiClient = ConnectionManager.getApiClient(serverId);
2020-05-16 22:02:52 +10:00
return new Promise((resolve, reject) => {
2020-07-21 13:25:50 +01:00
import('epubjs').then(({default: epubjs}) => {
const downloadHref = apiClient.getItemDownloadUrl(item.Id);
const book = epubjs(downloadHref, {openAs: 'epub'});
const rendition = this.render('viewer', book);
2020-09-01 22:55:08 +09:00
this.currentSrc = downloadHref;
this.rendition = rendition;
return rendition.display().then(() => {
const epubElem = document.querySelector('.epub-container');
epubElem.style.display = 'none';
this.bindEvents();
2020-05-16 22:02:52 +10:00
2020-09-01 22:55:08 +09:00
return this.rendition.book.locations.generate(1024).then(async () => {
if (this.cancellationToken) reject();
const percentageTicks = options.startPositionTicks / 10000000;
if (percentageTicks !== 0.0) {
const resumeLocation = book.locations.cfiFromPercentage(percentageTicks);
await rendition.display(resumeLocation);
}
2020-09-01 22:55:08 +09:00
this.loaded = true;
epubElem.style.display = 'block';
rendition.on('relocated', (locations) => {
2020-09-01 22:55:08 +09:00
this.progress = book.locations.percentageFromCfi(locations.start.cfi);
2020-09-08 02:05:02 -04:00
Events.trigger(this, 'timeupdate');
});
loading.hide();
return resolve();
});
}, () => {
console.error('failed to display epub');
return reject();
2020-05-16 22:02:52 +10:00
});
});
});
}
2020-05-16 22:02:52 +10:00
canPlayMediaType(mediaType) {
2020-05-16 22:02:52 +10:00
return (mediaType || '').toLowerCase() === 'book';
}
2020-05-30 13:00:51 +02:00
canPlayItem(item) {
2020-09-01 22:55:08 +09:00
if (item.Path && item.Path.endsWith('epub')) {
2020-05-30 13:00:51 +02:00
return true;
}
2020-06-06 15:53:47 +09:00
2020-05-30 13:00:51 +02:00
return false;
}
}
2020-05-16 22:02:52 +10:00
export default BookPlayer;