2020-11-19 16:42:21 -05:00
|
|
|
import ServerConnections from '../../components/ServerConnections';
|
2020-11-06 00:00:34 +00:00
|
|
|
import loading from '../../components/loading/loading';
|
|
|
|
import keyboardnavigation from '../../scripts/keyboardNavigation';
|
|
|
|
import dialogHelper from '../../components/dialogHelper/dialogHelper';
|
|
|
|
import dom from '../../scripts/dom';
|
|
|
|
import { appRouter } from '../../components/appRouter';
|
2023-03-08 11:03:48 -05:00
|
|
|
import { PluginType } from '../../types/plugin.ts';
|
2022-10-14 10:53:16 -04:00
|
|
|
import Events from '../../utils/events.ts';
|
|
|
|
|
2021-01-26 15:48:00 -05:00
|
|
|
import './style.scss';
|
2020-11-06 00:00:34 +00:00
|
|
|
import '../../elements/emby-button/paper-icon-button-light';
|
2020-09-01 22:55:08 +09:00
|
|
|
|
|
|
|
export class PdfPlayer {
|
|
|
|
constructor() {
|
|
|
|
this.name = 'PDF Player';
|
2023-03-08 11:03:48 -05:00
|
|
|
this.type = PluginType.MediaPlayer;
|
2020-09-01 22:55:08 +09:00
|
|
|
this.id = 'pdfplayer';
|
|
|
|
this.priority = 1;
|
|
|
|
|
|
|
|
this.onDialogClosed = this.onDialogClosed.bind(this);
|
|
|
|
this.onWindowKeyUp = this.onWindowKeyUp.bind(this);
|
|
|
|
this.onTouchStart = this.onTouchStart.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
play(options) {
|
|
|
|
this.progress = 0;
|
|
|
|
this.loaded = false;
|
|
|
|
this.cancellationToken = false;
|
|
|
|
this.pages = {};
|
|
|
|
|
|
|
|
loading.show();
|
|
|
|
|
2020-10-23 05:22:03 +09:00
|
|
|
const elem = this.createMediaElement();
|
2020-09-01 22:55:08 +09:00
|
|
|
return this.setCurrentSrc(elem, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
stop() {
|
|
|
|
this.unbindEvents();
|
|
|
|
|
2022-01-18 15:45:12 +00:00
|
|
|
const stopInfo = {
|
|
|
|
src: this.item
|
|
|
|
};
|
|
|
|
|
|
|
|
Events.trigger(this, 'stopped', [stopInfo]);
|
|
|
|
|
2020-10-23 05:22:03 +09:00
|
|
|
const elem = this.mediaElement;
|
2020-09-01 22:55:08 +09:00
|
|
|
if (elem) {
|
|
|
|
dialogHelper.close(elem);
|
|
|
|
this.mediaElement = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// hide loading animation
|
|
|
|
loading.hide();
|
|
|
|
|
|
|
|
// cancel page render
|
|
|
|
this.cancellationToken = true;
|
|
|
|
}
|
|
|
|
|
2022-01-18 15:45:12 +00:00
|
|
|
destroy() {
|
|
|
|
// Nothing to do here
|
|
|
|
}
|
|
|
|
|
2020-09-01 22:55:08 +09:00
|
|
|
currentItem() {
|
|
|
|
return this.item;
|
|
|
|
}
|
|
|
|
|
|
|
|
currentTime() {
|
|
|
|
return this.progress;
|
|
|
|
}
|
|
|
|
|
|
|
|
duration() {
|
|
|
|
return this.book ? this.book.numPages : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
volume() {
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
isMuted() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
paused() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
seekable() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
onWindowKeyUp(e) {
|
2020-10-23 05:22:03 +09:00
|
|
|
const key = keyboardnavigation.getKeyName(e);
|
2020-09-01 22:55:08 +09:00
|
|
|
|
|
|
|
if (!this.loaded) return;
|
|
|
|
switch (key) {
|
|
|
|
case 'l':
|
|
|
|
case 'ArrowRight':
|
|
|
|
case 'Right':
|
|
|
|
this.next();
|
|
|
|
break;
|
|
|
|
case 'j':
|
|
|
|
case 'ArrowLeft':
|
|
|
|
case 'Left':
|
|
|
|
this.previous();
|
|
|
|
break;
|
|
|
|
case 'Escape':
|
|
|
|
this.stop();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onTouchStart(e) {
|
|
|
|
if (!this.loaded || !e.touches || e.touches.length === 0) return;
|
|
|
|
if (e.touches[0].clientX < dom.getWindowSize().innerWidth / 2) {
|
|
|
|
this.previous();
|
|
|
|
} else {
|
|
|
|
this.next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onDialogClosed() {
|
|
|
|
this.stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
bindMediaElementEvents() {
|
2020-10-23 05:22:03 +09:00
|
|
|
const elem = this.mediaElement;
|
2020-09-01 22:55:08 +09:00
|
|
|
|
2022-01-18 15:45:12 +00:00
|
|
|
elem.addEventListener('close', this.onDialogClosed, { once: true });
|
|
|
|
elem.querySelector('.btnExit').addEventListener('click', this.onDialogClosed, { once: true });
|
2020-09-01 22:55:08 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
bindEvents() {
|
|
|
|
this.bindMediaElementEvents();
|
|
|
|
|
|
|
|
document.addEventListener('keyup', this.onWindowKeyUp);
|
|
|
|
document.addEventListener('touchstart', this.onTouchStart);
|
|
|
|
}
|
|
|
|
|
|
|
|
unbindMediaElementEvents() {
|
2020-10-23 05:22:03 +09:00
|
|
|
const elem = this.mediaElement;
|
2020-09-01 22:55:08 +09:00
|
|
|
|
|
|
|
elem.removeEventListener('close', this.onDialogClosed);
|
|
|
|
elem.querySelector('.btnExit').removeEventListener('click', this.onDialogClosed);
|
|
|
|
}
|
|
|
|
|
|
|
|
unbindEvents() {
|
|
|
|
if (this.mediaElement) {
|
|
|
|
this.unbindMediaElementEvents();
|
|
|
|
}
|
|
|
|
|
|
|
|
document.removeEventListener('keyup', this.onWindowKeyUp);
|
|
|
|
document.removeEventListener('touchstart', this.onTouchStart);
|
|
|
|
}
|
|
|
|
|
|
|
|
createMediaElement() {
|
|
|
|
let elem = this.mediaElement;
|
|
|
|
if (elem) {
|
|
|
|
return elem;
|
|
|
|
}
|
|
|
|
|
|
|
|
elem = document.getElementById('pdfPlayer');
|
|
|
|
if (!elem) {
|
|
|
|
elem = dialogHelper.createDialog({
|
|
|
|
exitAnimationDuration: 400,
|
|
|
|
size: 'fullscreen',
|
|
|
|
autoFocus: false,
|
|
|
|
scrollY: false,
|
|
|
|
exitAnimation: 'fadeout',
|
|
|
|
removeOnClose: true
|
|
|
|
});
|
|
|
|
|
|
|
|
let html = '';
|
|
|
|
html += '<canvas id="canvas"></canvas>';
|
|
|
|
html += '<div class="actionButtons">';
|
2022-02-24 20:15:24 +03:00
|
|
|
html += '<button is="paper-icon-button-light" class="autoSize btnExit" tabindex="-1"><span class="material-icons actionButtonIcon close" aria-hidden="true"></span></button>';
|
2020-09-01 22:55:08 +09:00
|
|
|
html += '</div>';
|
|
|
|
|
|
|
|
elem.id = 'pdfPlayer';
|
|
|
|
elem.innerHTML = html;
|
|
|
|
|
|
|
|
dialogHelper.open(elem);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.mediaElement = elem;
|
|
|
|
return elem;
|
|
|
|
}
|
|
|
|
|
|
|
|
setCurrentSrc(elem, options) {
|
2020-10-23 05:22:03 +09:00
|
|
|
const item = options.items[0];
|
2020-09-01 22:55:08 +09:00
|
|
|
|
|
|
|
this.item = item;
|
|
|
|
this.streamInfo = {
|
|
|
|
started: true,
|
|
|
|
ended: false,
|
2022-01-18 15:45:12 +00:00
|
|
|
item: this.item,
|
2020-09-01 22:55:08 +09:00
|
|
|
mediaSource: {
|
|
|
|
Id: item.Id
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-23 05:22:03 +09:00
|
|
|
const serverId = item.ServerId;
|
2020-11-19 14:08:14 -05:00
|
|
|
const apiClient = ServerConnections.getApiClient(serverId);
|
2020-09-01 22:55:08 +09:00
|
|
|
|
2022-09-16 03:06:13 -04:00
|
|
|
return import('pdfjs-dist').then(({ GlobalWorkerOptions, getDocument }) => {
|
2020-11-20 23:28:21 -05:00
|
|
|
const downloadHref = apiClient.getItemDownloadUrl(item.Id);
|
|
|
|
|
|
|
|
this.bindEvents();
|
|
|
|
GlobalWorkerOptions.workerSrc = appRouter.baseUrl() + '/libraries/pdf.worker.js';
|
|
|
|
|
|
|
|
const downloadTask = getDocument(downloadHref);
|
2022-09-16 03:06:13 -04:00
|
|
|
return downloadTask.promise.then(book => {
|
2020-11-20 23:28:21 -05:00
|
|
|
if (this.cancellationToken) return;
|
|
|
|
this.book = book;
|
|
|
|
this.loaded = true;
|
|
|
|
|
|
|
|
const percentageTicks = options.startPositionTicks / 10000;
|
|
|
|
if (percentageTicks !== 0) {
|
2021-05-06 19:51:17 +01:00
|
|
|
this.loadPage(percentageTicks + 1);
|
2020-11-20 23:28:21 -05:00
|
|
|
this.progress = percentageTicks;
|
|
|
|
} else {
|
|
|
|
this.loadPage(1);
|
|
|
|
}
|
2020-09-01 22:55:08 +09:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
next() {
|
|
|
|
if (this.progress === this.duration() - 1) return;
|
|
|
|
this.loadPage(this.progress + 2);
|
|
|
|
this.progress = this.progress + 1;
|
2022-01-18 15:45:12 +00:00
|
|
|
|
|
|
|
Events.trigger(this, 'pause');
|
2020-09-01 22:55:08 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
previous() {
|
|
|
|
if (this.progress === 0) return;
|
|
|
|
this.loadPage(this.progress);
|
|
|
|
this.progress = this.progress - 1;
|
2022-01-18 15:45:12 +00:00
|
|
|
|
|
|
|
Events.trigger(this, 'pause');
|
2020-09-01 22:55:08 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
replaceCanvas(canvas) {
|
|
|
|
const old = document.getElementById('canvas');
|
|
|
|
|
|
|
|
canvas.id = 'canvas';
|
|
|
|
old.parentNode.replaceChild(canvas, old);
|
|
|
|
}
|
|
|
|
|
|
|
|
loadPage(number) {
|
|
|
|
const prefix = 'page';
|
|
|
|
const pad = 2;
|
|
|
|
|
|
|
|
// generate list of cached pages by padding the requested page on both sides
|
2020-10-23 05:22:03 +09:00
|
|
|
const pages = [prefix + number];
|
2020-09-01 22:55:08 +09:00
|
|
|
for (let i = 1; i <= pad; i++) {
|
|
|
|
if (number - i > 0) pages.push(prefix + (number - i));
|
|
|
|
if (number + i < this.duration()) pages.push(prefix + (number + i));
|
|
|
|
}
|
|
|
|
|
|
|
|
// load any missing pages in the cache
|
2020-10-23 05:22:03 +09:00
|
|
|
for (const page of pages) {
|
2020-09-01 22:55:08 +09:00
|
|
|
if (!this.pages[page]) {
|
|
|
|
this.pages[page] = document.createElement('canvas');
|
2023-03-09 00:01:05 -05:00
|
|
|
this.renderPage(this.pages[page], parseInt(page.slice(4), 10));
|
2020-09-01 22:55:08 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// show the requested page
|
|
|
|
this.replaceCanvas(this.pages[prefix + number], number);
|
|
|
|
|
|
|
|
// delete all pages outside the cache area
|
2020-10-23 05:22:03 +09:00
|
|
|
for (const page in this.pages) {
|
2020-09-01 22:55:08 +09:00
|
|
|
if (!pages.includes(page)) {
|
|
|
|
delete this.pages[page];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
renderPage(canvas, number) {
|
|
|
|
this.book.getPage(number).then(page => {
|
2023-02-12 21:59:22 +09:00
|
|
|
const width = dom.getWindowSize().innerWidth;
|
|
|
|
const height = dom.getWindowSize().innerHeight;
|
|
|
|
const viewport = page.getViewport({ scale: 5 });
|
2020-09-01 22:55:08 +09:00
|
|
|
const context = canvas.getContext('2d');
|
|
|
|
canvas.width = viewport.width;
|
|
|
|
canvas.height = viewport.height;
|
2023-02-13 05:41:22 +09:00
|
|
|
|
|
|
|
if (width < height) {
|
|
|
|
canvas.style.width = '100%';
|
|
|
|
canvas.style.height = 'auto';
|
2023-02-12 21:59:22 +09:00
|
|
|
} else {
|
2023-02-13 05:41:22 +09:00
|
|
|
canvas.style.height = '100%';
|
|
|
|
canvas.style.width = 'auto';
|
2023-02-13 05:34:38 +09:00
|
|
|
}
|
2023-02-13 05:41:22 +09:00
|
|
|
|
2020-10-23 05:22:03 +09:00
|
|
|
const renderContext = {
|
2020-09-01 22:55:08 +09:00
|
|
|
canvasContext: context,
|
|
|
|
viewport: viewport
|
|
|
|
};
|
|
|
|
|
2020-10-23 05:22:03 +09:00
|
|
|
const renderTask = page.render(renderContext);
|
2020-09-01 22:55:08 +09:00
|
|
|
renderTask.promise.then(() => {
|
|
|
|
loading.hide();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
canPlayMediaType(mediaType) {
|
|
|
|
return (mediaType || '').toLowerCase() === 'book';
|
|
|
|
}
|
|
|
|
|
|
|
|
canPlayItem(item) {
|
2022-10-06 01:13:06 -04:00
|
|
|
return item.Path && item.Path.endsWith('pdf');
|
2020-09-01 22:55:08 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default PdfPlayer;
|