2020-08-14 08:46:34 +02:00
|
|
|
import connectionManager from 'jellyfin-apiclient';
|
|
|
|
import loading from '../../components/loading/loading';
|
2020-05-22 10:39:21 +09:00
|
|
|
import keyboardnavigation from 'keyboardnavigation';
|
|
|
|
import dialogHelper from 'dialogHelper';
|
2020-07-29 19:36:44 +09:00
|
|
|
import dom from 'dom';
|
2020-08-14 08:46:34 +02:00
|
|
|
import events from 'jellyfin-apiclient';
|
|
|
|
import './style';
|
|
|
|
import 'material-design-icons-iconfont';
|
|
|
|
import '../../elements/emby-button/paper-icon-button-light';
|
2020-05-22 10:39:21 +09:00
|
|
|
|
2020-06-06 15:53:47 +09:00
|
|
|
import TableOfContents from './tableOfContents';
|
2020-05-28 21:14:25 +10:00
|
|
|
|
2020-05-22 10:39:21 +09:00
|
|
|
export class BookPlayer {
|
2020-05-22 10:42:58 +09:00
|
|
|
constructor() {
|
|
|
|
this.name = 'Book Player';
|
|
|
|
this.type = 'mediaplayer';
|
|
|
|
this.id = 'bookplayer';
|
|
|
|
this.priority = 1;
|
2020-05-28 21:14:25 +10:00
|
|
|
|
|
|
|
this.onDialogClosed = this.onDialogClosed.bind(this);
|
|
|
|
this.openTableOfContents = this.openTableOfContents.bind(this);
|
2020-09-14 18:51:03 +10:00
|
|
|
this.prevChapter = this.prevChapter.bind(this);
|
|
|
|
this.nextChapter = this.nextChapter.bind(this);
|
2020-05-28 21:14:25 +10:00
|
|
|
this.onWindowKeyUp = this.onWindowKeyUp.bind(this);
|
2020-05-22 10:42:58 +09:00
|
|
|
}
|
2020-05-16 22:02:52 +10:00
|
|
|
|
2020-05-22 10:42:58 +09:00
|
|
|
play(options) {
|
2020-09-01 22:55:08 +09:00
|
|
|
this.progress = 0;
|
|
|
|
this.cancellationToken = false;
|
|
|
|
this.loaded = false;
|
2020-05-29 19:13:02 +10:00
|
|
|
|
2020-05-22 10:42:58 +09:00
|
|
|
loading.show();
|
2020-07-16 22:19:09 +02:00
|
|
|
const elem = this.createMediaElement();
|
2020-05-22 10:42:58 +09:00
|
|
|
return this.setCurrentSrc(elem, options);
|
|
|
|
}
|
2020-05-16 22:02:52 +10:00
|
|
|
|
2020-05-22 10:42:58 +09:00
|
|
|
stop() {
|
2020-05-28 21:14:25 +10:00
|
|
|
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
|
|
|
|
2020-05-28 18:38:13 +10:00
|
|
|
if (elem) {
|
|
|
|
dialogHelper.close(elem);
|
2020-09-01 22:55:08 +09:00
|
|
|
this.mediaElement = null;
|
2020-05-22 10:39:21 +09:00
|
|
|
}
|
2020-05-28 18:38:13 +10:00
|
|
|
|
|
|
|
if (tocElement) {
|
2020-05-28 21:14:25 +10:00
|
|
|
tocElement.destroy();
|
2020-09-01 22:55:08 +09:00
|
|
|
this.tocElement = null;
|
2020-05-28 18:38:13 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
if (rendition) {
|
|
|
|
rendition.destroy();
|
|
|
|
}
|
2020-05-29 19:13:02 +10:00
|
|
|
|
2020-09-01 22:55:08 +09:00
|
|
|
// hide loader in case player was not fully loaded yet
|
2020-05-29 19:13:02 +10:00
|
|
|
loading.hide();
|
2020-09-01 22:55:08 +09:00
|
|
|
this.cancellationToken = true;
|
2020-05-29 19:13:02 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
currentItem() {
|
2020-09-01 22:55:08 +09:00
|
|
|
return this.item;
|
2020-05-29 19:13:02 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
currentTime() {
|
2020-09-01 22:55:08 +09:00
|
|
|
return this.progress * 1000;
|
2020-05-29 19:13:02 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
duration() {
|
|
|
|
return 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
getBufferedRanges() {
|
|
|
|
return [{
|
|
|
|
start: 0,
|
|
|
|
end: 10000000
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
volume() {
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
isMuted() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
paused() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
seekable() {
|
|
|
|
return true;
|
2020-05-22 10:42:58 +09:00
|
|
|
}
|
2020-05-16 22:02:52 +10:00
|
|
|
|
2020-05-22 10:42:58 +09:00
|
|
|
onWindowKeyUp(e) {
|
2020-07-16 22:19:09 +02:00
|
|
|
const key = keyboardnavigation.getKeyName(e);
|
2020-09-01 22:55:08 +09:00
|
|
|
const rendition = this.rendition;
|
2020-07-16 22:19:09 +02:00
|
|
|
const book = rendition.book;
|
2020-05-22 10:42:58 +09:00
|
|
|
|
2020-09-01 22:55:08 +09:00
|
|
|
if (!this.loaded) return;
|
2020-05-22 10:42:58 +09:00
|
|
|
switch (key) {
|
|
|
|
case 'l':
|
|
|
|
case 'ArrowRight':
|
|
|
|
case 'Right':
|
2020-07-29 19:36:44 +09:00
|
|
|
book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next();
|
2020-05-22 10:42:58 +09:00
|
|
|
break;
|
|
|
|
case 'j':
|
|
|
|
case 'ArrowLeft':
|
|
|
|
case 'Left':
|
2020-07-29 19:36:44 +09:00
|
|
|
book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev();
|
2020-05-22 10:42:58 +09:00
|
|
|
break;
|
|
|
|
case 'Escape':
|
2020-09-01 22:55:08 +09:00
|
|
|
if (this.tocElement) {
|
2020-05-28 21:14:25 +10:00
|
|
|
// Close table of contents on ESC if it is open
|
2020-09-01 22:55:08 +09:00
|
|
|
this.tocElement.destroy();
|
2020-05-28 21:14:25 +10:00
|
|
|
} else {
|
|
|
|
// Otherwise stop the entire book player
|
|
|
|
this.stop();
|
2020-05-22 10:42:58 +09:00
|
|
|
}
|
|
|
|
break;
|
2020-05-16 22:02:52 +10:00
|
|
|
}
|
2020-05-22 10:42:58 +09:00
|
|
|
}
|
2020-05-16 22:02:52 +10:00
|
|
|
|
2020-05-22 10:42:58 +09:00
|
|
|
onDialogClosed() {
|
|
|
|
this.stop();
|
|
|
|
}
|
2020-05-16 22:02:52 +10:00
|
|
|
|
2020-05-28 21:14:25 +10:00
|
|
|
bindMediaElementEvents() {
|
2020-09-01 22:55:08 +09:00
|
|
|
const elem = this.mediaElement;
|
2020-05-19 16:05:44 +10:00
|
|
|
|
2020-05-28 21:14:25 +10:00
|
|
|
elem.addEventListener('close', this.onDialogClosed, {once: true});
|
2020-09-14 18:51:03 +10:00
|
|
|
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-28 21:14:25 +10:00
|
|
|
}
|
2020-05-19 16:05:44 +10:00
|
|
|
|
2020-05-28 21:14:25 +10:00
|
|
|
bindEvents() {
|
|
|
|
this.bindMediaElementEvents();
|
|
|
|
|
|
|
|
document.addEventListener('keyup', this.onWindowKeyUp);
|
2020-07-29 19:36:44 +09:00
|
|
|
|
2020-05-28 21:14:25 +10:00
|
|
|
// 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);
|
2020-05-28 21:14:25 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
unbindMediaElementEvents() {
|
2020-09-01 22:55:08 +09:00
|
|
|
const elem = this.mediaElement;
|
2020-05-28 21:14:25 +10:00
|
|
|
|
|
|
|
elem.removeEventListener('close', this.onDialogClosed);
|
2020-09-14 18:51:03 +10:00
|
|
|
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);
|
|
|
|
}
|
2020-05-28 21:14:25 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
unbindEvents() {
|
2020-09-01 22:55:08 +09:00
|
|
|
if (this.mediaElement) {
|
2020-05-28 21:14:25 +10:00
|
|
|
this.unbindMediaElementEvents();
|
|
|
|
}
|
2020-07-29 19:36:44 +09:00
|
|
|
|
2020-05-28 21:14:25 +10:00
|
|
|
document.removeEventListener('keyup', this.onWindowKeyUp);
|
2020-07-29 19:36:44 +09:00
|
|
|
|
2020-09-01 22:55:08 +09:00
|
|
|
if (this.rendition) {
|
|
|
|
this.rendition.off('keyup', this.onWindowKeyUp);
|
2020-05-28 21:14:25 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
openTableOfContents() {
|
2020-09-01 22:55:08 +09:00
|
|
|
if (this.loaded) {
|
|
|
|
this.tocElement = new TableOfContents(this);
|
2020-05-29 19:13:02 +10:00
|
|
|
}
|
2020-05-22 10:42:58 +09:00
|
|
|
}
|
|
|
|
|
2020-09-14 18:51:03 +10:00
|
|
|
prevChapter(e) {
|
|
|
|
this._rendition.prev();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
nextChapter(e) {
|
|
|
|
this._rendition.next();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
|
2020-05-22 10:42:58 +09:00
|
|
|
createMediaElement() {
|
2020-09-01 22:55:08 +09:00
|
|
|
let elem = this.mediaElement;
|
2020-05-22 10:42:58 +09:00
|
|
|
if (elem) {
|
|
|
|
return elem;
|
2020-05-19 16:05:44 +10:00
|
|
|
}
|
|
|
|
|
2020-05-22 10:42:58 +09:00
|
|
|
elem = document.getElementById('bookPlayer');
|
|
|
|
if (!elem) {
|
|
|
|
elem = dialogHelper.createDialog({
|
|
|
|
exitAnimationDuration: 400,
|
|
|
|
size: 'fullscreen',
|
|
|
|
autoFocus: false,
|
|
|
|
scrollY: false,
|
|
|
|
exitAnimation: 'fadeout',
|
|
|
|
removeOnClose: true
|
|
|
|
});
|
2020-07-29 19:36:44 +09:00
|
|
|
|
2020-05-22 10:42:58 +09:00
|
|
|
let html = '';
|
2020-09-14 18:51:03 +10:00
|
|
|
|
|
|
|
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>';
|
2020-05-22 10:42:58 +09:00
|
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
2020-05-16 22:02:52 +10:00
|
|
|
|
2020-09-14 18:51:03 +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';
|
2020-05-22 10:42:58 +09:00
|
|
|
elem.innerHTML = html;
|
2020-05-16 22:02:52 +10:00
|
|
|
|
2020-05-22 10:42:58 +09:00
|
|
|
dialogHelper.open(elem);
|
2020-05-16 22:02:52 +10:00
|
|
|
}
|
|
|
|
|
2020-09-01 22:55:08 +09:00
|
|
|
this.mediaElement = elem;
|
2020-05-22 10:42:58 +09:00
|
|
|
return elem;
|
|
|
|
}
|
2020-05-16 22:02:52 +10:00
|
|
|
|
2020-06-08 17:44:55 +10:00
|
|
|
render(elem, book) {
|
|
|
|
if (browser.mobile) {
|
|
|
|
return book.renderTo(elem, {
|
|
|
|
width: '100%',
|
|
|
|
height: '100%',
|
2020-09-14 18:51:03 +10:00
|
|
|
flow: 'scrolled-doc'
|
2020-06-08 17:44:55 +10:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return book.renderTo(elem, {
|
|
|
|
width: '100%',
|
|
|
|
height: '100%'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-22 10:42:58 +09:00
|
|
|
setCurrentSrc(elem, options) {
|
2020-07-16 22:19:09 +02:00
|
|
|
const item = options.items[0];
|
2020-09-01 22:55:08 +09:00
|
|
|
this.item = item;
|
2020-05-29 19:13:02 +10:00
|
|
|
this.streamInfo = {
|
|
|
|
started: true,
|
|
|
|
ended: false,
|
|
|
|
mediaSource: {
|
|
|
|
Id: item.Id
|
|
|
|
}
|
|
|
|
};
|
2020-05-28 18:39:49 +10:00
|
|
|
|
2020-07-16 22:19:09 +02:00
|
|
|
const serverId = item.ServerId;
|
2020-08-30 06:06:47 +02:00
|
|
|
const apiClient = window.connectionManager.getApiClient(serverId);
|
2020-05-16 22:02:52 +10:00
|
|
|
|
2020-05-28 18:45:28 +10:00
|
|
|
return new Promise((resolve, reject) => {
|
2020-07-21 13:25:50 +01:00
|
|
|
import('epubjs').then(({default: epubjs}) => {
|
2020-07-16 22:19:09 +02:00
|
|
|
const downloadHref = apiClient.getItemDownloadUrl(item.Id);
|
|
|
|
const book = epubjs(downloadHref, {openAs: 'epub'});
|
2020-09-14 18:51:03 +10:00
|
|
|
const rendition = this.render('viewer', book);
|
2020-07-29 19:36:44 +09:00
|
|
|
|
2020-09-01 22:55:08 +09:00
|
|
|
this.currentSrc = downloadHref;
|
|
|
|
this.rendition = rendition;
|
2020-05-29 19:13:02 +10:00
|
|
|
|
2020-05-28 18:45:28 +10:00
|
|
|
return rendition.display().then(() => {
|
2020-07-16 22:19:09 +02:00
|
|
|
const epubElem = document.querySelector('.epub-container');
|
2020-05-29 19:13:02 +10:00
|
|
|
epubElem.style.display = 'none';
|
|
|
|
|
2020-05-28 21:14:25 +10:00
|
|
|
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();
|
2020-05-29 19:13:02 +10:00
|
|
|
|
2020-06-06 19:02:08 +10:00
|
|
|
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;
|
2020-05-29 19:13:02 +10:00
|
|
|
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-05-29 19:13:02 +10:00
|
|
|
events.trigger(this, 'timeupdate');
|
|
|
|
});
|
|
|
|
|
|
|
|
loading.hide();
|
|
|
|
return resolve();
|
|
|
|
});
|
2020-05-28 18:45:28 +10:00
|
|
|
}, () => {
|
2020-07-29 19:36:44 +09:00
|
|
|
console.error('failed to display epub');
|
2020-05-22 10:42:58 +09:00
|
|
|
return reject();
|
2020-05-16 22:02:52 +10:00
|
|
|
});
|
|
|
|
});
|
2020-05-22 10:42:58 +09:00
|
|
|
});
|
|
|
|
}
|
2020-05-16 22:02:52 +10:00
|
|
|
|
2020-05-22 10:39:21 +09:00
|
|
|
canPlayMediaType(mediaType) {
|
2020-05-16 22:02:52 +10:00
|
|
|
return (mediaType || '').toLowerCase() === 'book';
|
2020-05-22 10:39:21 +09:00
|
|
|
}
|
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-22 10:39:21 +09:00
|
|
|
}
|
2020-05-16 22:02:52 +10:00
|
|
|
|
2020-05-22 10:39:21 +09:00
|
|
|
export default BookPlayer;
|