add basic pdf reader
This commit is contained in:
parent
4d04d54104
commit
108ebc58a6
11 changed files with 400 additions and 69 deletions
|
@ -62,6 +62,7 @@
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"date-fns": "^2.16.0",
|
"date-fns": "^2.16.0",
|
||||||
"epubjs": "^0.3.85",
|
"epubjs": "^0.3.85",
|
||||||
|
"pdfjs-dist": "^2.4.456",
|
||||||
"fast-text-encoding": "^1.0.3",
|
"fast-text-encoding": "^1.0.3",
|
||||||
"flv.js": "^1.5.0",
|
"flv.js": "^1.5.0",
|
||||||
"headroom.js": "^0.11.0",
|
"headroom.js": "^0.11.0",
|
||||||
|
@ -323,6 +324,7 @@
|
||||||
"src/libraries/scroller.js",
|
"src/libraries/scroller.js",
|
||||||
"src/plugins/backdropScreensaver/plugin.js",
|
"src/plugins/backdropScreensaver/plugin.js",
|
||||||
"src/plugins/bookPlayer/plugin.js",
|
"src/plugins/bookPlayer/plugin.js",
|
||||||
|
"src/plugins/pdfPlayer/plugin.js",
|
||||||
"src/plugins/bookPlayer/tableOfContents.js",
|
"src/plugins/bookPlayer/tableOfContents.js",
|
||||||
"src/plugins/chromecastPlayer/chromecastHelper.js",
|
"src/plugins/chromecastPlayer/chromecastHelper.js",
|
||||||
"src/plugins/photoPlayer/plugin.js",
|
"src/plugins/photoPlayer/plugin.js",
|
||||||
|
|
|
@ -101,6 +101,11 @@ _define('epubjs', function () {
|
||||||
return epubjs;
|
return epubjs;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var pdfjs = require('pdfjs-dist/build/pdf');
|
||||||
|
_define('pdfjs', function () {
|
||||||
|
return pdfjs;
|
||||||
|
});
|
||||||
|
|
||||||
// page.js
|
// page.js
|
||||||
const page = require('page');
|
const page = require('page');
|
||||||
_define('page', function() {
|
_define('page', function() {
|
||||||
|
|
|
@ -172,7 +172,6 @@ function supportsCue() {
|
||||||
function onAppVisible() {
|
function onAppVisible() {
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
isHidden = false;
|
isHidden = false;
|
||||||
console.debug('triggering app resume event');
|
|
||||||
events.trigger(appHost, 'resume');
|
events.trigger(appHost, 'resume');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +179,6 @@ function onAppVisible() {
|
||||||
function onAppHidden() {
|
function onAppHidden() {
|
||||||
if (!isHidden) {
|
if (!isHidden) {
|
||||||
isHidden = true;
|
isHidden = true;
|
||||||
console.debug('app is hidden');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
"plugins/bookPlayer/plugin",
|
"plugins/bookPlayer/plugin",
|
||||||
"plugins/youtubePlayer/plugin",
|
"plugins/youtubePlayer/plugin",
|
||||||
"plugins/backdropScreensaver/plugin",
|
"plugins/backdropScreensaver/plugin",
|
||||||
|
"plugins/pdfPlayer/plugin",
|
||||||
"plugins/logoScreensaver/plugin",
|
"plugins/logoScreensaver/plugin",
|
||||||
"plugins/sessionPlayer/plugin",
|
"plugins/sessionPlayer/plugin",
|
||||||
"plugins/chromecastPlayer/plugin"
|
"plugins/chromecastPlayer/plugin"
|
||||||
|
|
|
@ -20,11 +20,13 @@ export class BookPlayer {
|
||||||
this.onDialogClosed = this.onDialogClosed.bind(this);
|
this.onDialogClosed = this.onDialogClosed.bind(this);
|
||||||
this.openTableOfContents = this.openTableOfContents.bind(this);
|
this.openTableOfContents = this.openTableOfContents.bind(this);
|
||||||
this.onWindowKeyUp = this.onWindowKeyUp.bind(this);
|
this.onWindowKeyUp = this.onWindowKeyUp.bind(this);
|
||||||
|
this.onTouchStart = this.onTouchStart.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
play(options) {
|
play(options) {
|
||||||
this._progress = 0;
|
this.progress = 0;
|
||||||
this._loaded = false;
|
this.cancellationToken = false;
|
||||||
|
this.loaded = false;
|
||||||
|
|
||||||
loading.show();
|
loading.show();
|
||||||
const elem = this.createMediaElement();
|
const elem = this.createMediaElement();
|
||||||
|
@ -34,35 +36,35 @@ export class BookPlayer {
|
||||||
stop() {
|
stop() {
|
||||||
this.unbindEvents();
|
this.unbindEvents();
|
||||||
|
|
||||||
const elem = this._mediaElement;
|
const elem = this.mediaElement;
|
||||||
const tocElement = this._tocElement;
|
const tocElement = this.tocElement;
|
||||||
const rendition = this._rendition;
|
const rendition = this.rendition;
|
||||||
|
|
||||||
if (elem) {
|
if (elem) {
|
||||||
dialogHelper.close(elem);
|
dialogHelper.close(elem);
|
||||||
this._mediaElement = null;
|
this.mediaElement = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tocElement) {
|
if (tocElement) {
|
||||||
tocElement.destroy();
|
tocElement.destroy();
|
||||||
this._tocElement = null;
|
this.tocElement = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rendition) {
|
if (rendition) {
|
||||||
rendition.destroy();
|
rendition.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide loader in case player was not fully loaded yet
|
// hide loader in case player was not fully loaded yet
|
||||||
loading.hide();
|
loading.hide();
|
||||||
this._cancellationToken.shouldCancel = true;
|
this.cancellationToken = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentItem() {
|
currentItem() {
|
||||||
return this._currentItem;
|
return this.item;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTime() {
|
currentTime() {
|
||||||
return this._progress * 1000;
|
return this.progress * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
duration() {
|
duration() {
|
||||||
|
@ -94,12 +96,10 @@ export class BookPlayer {
|
||||||
|
|
||||||
onWindowKeyUp(e) {
|
onWindowKeyUp(e) {
|
||||||
const key = keyboardnavigation.getKeyName(e);
|
const key = keyboardnavigation.getKeyName(e);
|
||||||
|
const rendition = this.rendition;
|
||||||
// TODO: depending on the event this can be the document or the rendition itself
|
|
||||||
const rendition = this._rendition || this;
|
|
||||||
const book = rendition.book;
|
const book = rendition.book;
|
||||||
|
|
||||||
if (this._loaded === false) return;
|
if (!this.loaded) return;
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'l':
|
case 'l':
|
||||||
case 'ArrowRight':
|
case 'ArrowRight':
|
||||||
|
@ -112,9 +112,9 @@ export class BookPlayer {
|
||||||
book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev();
|
book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev();
|
||||||
break;
|
break;
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
if (this._tocElement) {
|
if (this.tocElement) {
|
||||||
// Close table of contents on ESC if it is open
|
// Close table of contents on ESC if it is open
|
||||||
this._tocElement.destroy();
|
this.tocElement.destroy();
|
||||||
} else {
|
} else {
|
||||||
// Otherwise stop the entire book player
|
// Otherwise stop the entire book player
|
||||||
this.stop();
|
this.stop();
|
||||||
|
@ -124,17 +124,12 @@ export class BookPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
onTouchStart(e) {
|
onTouchStart(e) {
|
||||||
// TODO: depending on the event this can be the document or the rendition itself
|
const rendition = this.rendition;
|
||||||
const rendition = this._rendition || this;
|
|
||||||
const book = rendition.book;
|
const book = rendition.book;
|
||||||
|
|
||||||
// check that the event is from the book or the document
|
|
||||||
if (!book || this._loaded === false) return;
|
|
||||||
|
|
||||||
// epubjs stores pages off the screen or something for preloading
|
// epubjs stores pages off the screen or something for preloading
|
||||||
// get the modulus of the touch event to account for the increased width
|
// get the modulus of the touch event to account for the increased width
|
||||||
if (!e.touches || e.touches.length === 0) return;
|
if (!this.loaded || !e.touches || e.touches.length === 0) return;
|
||||||
|
|
||||||
const touch = e.touches[0].clientX % dom.getWindowSize().innerWidth;
|
const touch = e.touches[0].clientX % dom.getWindowSize().innerWidth;
|
||||||
if (touch < dom.getWindowSize().innerWidth / 2) {
|
if (touch < dom.getWindowSize().innerWidth / 2) {
|
||||||
book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev();
|
book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev();
|
||||||
|
@ -148,7 +143,7 @@ export class BookPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
bindMediaElementEvents() {
|
bindMediaElementEvents() {
|
||||||
const elem = this._mediaElement;
|
const elem = this.mediaElement;
|
||||||
|
|
||||||
elem.addEventListener('close', this.onDialogClosed, {once: true});
|
elem.addEventListener('close', this.onDialogClosed, {once: true});
|
||||||
elem.querySelector('.btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true});
|
elem.querySelector('.btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true});
|
||||||
|
@ -161,13 +156,13 @@ export class BookPlayer {
|
||||||
document.addEventListener('keyup', this.onWindowKeyUp);
|
document.addEventListener('keyup', this.onWindowKeyUp);
|
||||||
document.addEventListener('touchstart', this.onTouchStart);
|
document.addEventListener('touchstart', this.onTouchStart);
|
||||||
|
|
||||||
// FIXME: I don't really get why document keyup event is not triggered when epub is in focus
|
// FIXME: document keyup event is not triggered when epub is in focus
|
||||||
this._rendition.on('keyup', this.onWindowKeyUp);
|
this.rendition.on('keyup', this.onWindowKeyUp);
|
||||||
this._rendition.on('touchstart', this.onTouchStart);
|
this.rendition.on('touchstart', this.onTouchStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
unbindMediaElementEvents() {
|
unbindMediaElementEvents() {
|
||||||
const elem = this._mediaElement;
|
const elem = this.mediaElement;
|
||||||
|
|
||||||
elem.removeEventListener('close', this.onDialogClosed);
|
elem.removeEventListener('close', this.onDialogClosed);
|
||||||
elem.querySelector('.btnBookplayerExit').removeEventListener('click', this.onDialogClosed);
|
elem.querySelector('.btnBookplayerExit').removeEventListener('click', this.onDialogClosed);
|
||||||
|
@ -175,27 +170,27 @@ export class BookPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
unbindEvents() {
|
unbindEvents() {
|
||||||
if (this._mediaElement) {
|
if (this.mediaElement) {
|
||||||
this.unbindMediaElementEvents();
|
this.unbindMediaElementEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.removeEventListener('keyup', this.onWindowKeyUp);
|
document.removeEventListener('keyup', this.onWindowKeyUp);
|
||||||
document.removeEventListener('touchstart', this.onTouchStart);
|
document.removeEventListener('touchstart', this.onTouchStart);
|
||||||
|
|
||||||
if (this._rendition) {
|
if (this.rendition) {
|
||||||
this._rendition.off('keyup', this.onWindowKeyUp);
|
this.rendition.off('keyup', this.onWindowKeyUp);
|
||||||
this._rendition.off('touchstart', this.onTouchStart);
|
this.rendition.off('touchstart', this.onTouchStart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openTableOfContents() {
|
openTableOfContents() {
|
||||||
if (this._loaded) {
|
if (this.loaded) {
|
||||||
this._tocElement = new TableOfContents(this);
|
this.tocElement = new TableOfContents(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createMediaElement() {
|
createMediaElement() {
|
||||||
let elem = this._mediaElement;
|
let elem = this.mediaElement;
|
||||||
if (elem) {
|
if (elem) {
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
@ -211,8 +206,6 @@ export class BookPlayer {
|
||||||
removeOnClose: true
|
removeOnClose: true
|
||||||
});
|
});
|
||||||
|
|
||||||
elem.id = 'bookPlayer';
|
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
html += '<div class="topRightActionButtons">';
|
html += '<div class="topRightActionButtons">';
|
||||||
html += '<button is="paper-icon-button-light" class="autoSize bookplayerButton btnBookplayerExit hide-mouse-idle-tv" tabindex="-1"><i class="material-icons bookplayerButtonIcon close"></i></button>';
|
html += '<button is="paper-icon-button-light" class="autoSize bookplayerButton btnBookplayerExit hide-mouse-idle-tv" tabindex="-1"><i class="material-icons bookplayerButtonIcon close"></i></button>';
|
||||||
|
@ -221,19 +214,19 @@ export class BookPlayer {
|
||||||
html += '<button is="paper-icon-button-light" class="autoSize bookplayerButton btnBookplayerToc hide-mouse-idle-tv" tabindex="-1"><i class="material-icons bookplayerButtonIcon toc"></i></button>';
|
html += '<button is="paper-icon-button-light" class="autoSize bookplayerButton btnBookplayerToc hide-mouse-idle-tv" tabindex="-1"><i class="material-icons bookplayerButtonIcon toc"></i></button>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
|
elem.id = 'bookPlayer';
|
||||||
elem.innerHTML = html;
|
elem.innerHTML = html;
|
||||||
|
|
||||||
dialogHelper.open(elem);
|
dialogHelper.open(elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._mediaElement = elem;
|
this.mediaElement = elem;
|
||||||
|
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentSrc(elem, options) {
|
setCurrentSrc(elem, options) {
|
||||||
const item = options.items[0];
|
const item = options.items[0];
|
||||||
this._currentItem = item;
|
this.item = item;
|
||||||
this.streamInfo = {
|
this.streamInfo = {
|
||||||
started: true,
|
started: true,
|
||||||
ended: false,
|
ended: false,
|
||||||
|
@ -249,15 +242,10 @@ export class BookPlayer {
|
||||||
import('epubjs').then(({default: epubjs}) => {
|
import('epubjs').then(({default: epubjs}) => {
|
||||||
const downloadHref = apiClient.getItemDownloadUrl(item.Id);
|
const downloadHref = apiClient.getItemDownloadUrl(item.Id);
|
||||||
const book = epubjs(downloadHref, {openAs: 'epub'});
|
const book = epubjs(downloadHref, {openAs: 'epub'});
|
||||||
const rendition = book.renderTo(elem, {width: '100%', height: '97%'});
|
const rendition = book.renderTo(elem, {width: '100%', height: '96%'});
|
||||||
|
|
||||||
this._currentSrc = downloadHref;
|
this.currentSrc = downloadHref;
|
||||||
this._rendition = rendition;
|
this.rendition = rendition;
|
||||||
const cancellationToken = {
|
|
||||||
shouldCancel: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this._cancellationToken = cancellationToken;
|
|
||||||
|
|
||||||
return rendition.display().then(() => {
|
return rendition.display().then(() => {
|
||||||
const epubElem = document.querySelector('.epub-container');
|
const epubElem = document.querySelector('.epub-container');
|
||||||
|
@ -265,10 +253,8 @@ export class BookPlayer {
|
||||||
|
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
|
|
||||||
return this._rendition.book.locations.generate(1024).then(async () => {
|
return this.rendition.book.locations.generate(1024).then(async () => {
|
||||||
if (cancellationToken.shouldCancel) {
|
if (this.cancellationToken) reject();
|
||||||
return reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
const percentageTicks = options.startPositionTicks / 10000000;
|
const percentageTicks = options.startPositionTicks / 10000000;
|
||||||
if (percentageTicks !== 0.0) {
|
if (percentageTicks !== 0.0) {
|
||||||
|
@ -276,15 +262,14 @@ export class BookPlayer {
|
||||||
await rendition.display(resumeLocation);
|
await rendition.display(resumeLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._loaded = true;
|
this.loaded = true;
|
||||||
epubElem.style.display = 'block';
|
epubElem.style.display = 'block';
|
||||||
rendition.on('relocated', (locations) => {
|
rendition.on('relocated', (locations) => {
|
||||||
this._progress = book.locations.percentageFromCfi(locations.start.cfi);
|
this.progress = book.locations.percentageFromCfi(locations.start.cfi);
|
||||||
events.trigger(this, 'timeupdate');
|
events.trigger(this, 'timeupdate');
|
||||||
});
|
});
|
||||||
|
|
||||||
loading.hide();
|
loading.hide();
|
||||||
|
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
}, () => {
|
}, () => {
|
||||||
|
@ -300,7 +285,7 @@ export class BookPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
canPlayItem(item) {
|
canPlayItem(item) {
|
||||||
if (item.Path && (item.Path.endsWith('epub'))) {
|
if (item.Path && item.Path.endsWith('epub')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ import dialogHelper from 'dialogHelper';
|
||||||
|
|
||||||
export default class TableOfContents {
|
export default class TableOfContents {
|
||||||
constructor(bookPlayer) {
|
constructor(bookPlayer) {
|
||||||
this._bookPlayer = bookPlayer;
|
this.bookPlayer = bookPlayer;
|
||||||
this._rendition = bookPlayer._rendition;
|
this.rendition = bookPlayer.rendition;
|
||||||
|
|
||||||
this.onDialogClosed = this.onDialogClosed.bind(this);
|
this.onDialogClosed = this.onDialogClosed.bind(this);
|
||||||
|
|
||||||
|
@ -11,24 +11,24 @@ export default class TableOfContents {
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
const elem = this._elem;
|
const elem = this.elem;
|
||||||
if (elem) {
|
if (elem) {
|
||||||
this.unbindEvents();
|
this.unbindEvents();
|
||||||
dialogHelper.close(elem);
|
dialogHelper.close(elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._bookPlayer._tocElement = null;
|
this.bookPlayer.tocElement = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
const elem = this._elem;
|
const elem = this.elem;
|
||||||
|
|
||||||
elem.addEventListener('close', this.onDialogClosed, {once: true});
|
elem.addEventListener('close', this.onDialogClosed, {once: true});
|
||||||
elem.querySelector('.btnBookplayerTocClose').addEventListener('click', this.onDialogClosed, {once: true});
|
elem.querySelector('.btnBookplayerTocClose').addEventListener('click', this.onDialogClosed, {once: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
unbindEvents() {
|
unbindEvents() {
|
||||||
const elem = this._elem;
|
const elem = this.elem;
|
||||||
|
|
||||||
elem.removeEventListener('close', this.onDialogClosed);
|
elem.removeEventListener('close', this.onDialogClosed);
|
||||||
elem.querySelector('.btnBookplayerTocClose').removeEventListener('click', this.onDialogClosed);
|
elem.querySelector('.btnBookplayerTocClose').removeEventListener('click', this.onDialogClosed);
|
||||||
|
@ -52,7 +52,7 @@ export default class TableOfContents {
|
||||||
}
|
}
|
||||||
|
|
||||||
createMediaElement() {
|
createMediaElement() {
|
||||||
const rendition = this._rendition;
|
const rendition = this.rendition;
|
||||||
|
|
||||||
const elem = dialogHelper.createDialog({
|
const elem = dialogHelper.createDialog({
|
||||||
size: 'small',
|
size: 'small',
|
||||||
|
@ -68,7 +68,8 @@ export default class TableOfContents {
|
||||||
tocHtml += '<ul class="toc">';
|
tocHtml += '<ul class="toc">';
|
||||||
rendition.book.navigation.forEach((chapter) => {
|
rendition.book.navigation.forEach((chapter) => {
|
||||||
tocHtml += '<li>';
|
tocHtml += '<li>';
|
||||||
// Remove '../' from href
|
|
||||||
|
// remove parent directory reference from href to fix certain books
|
||||||
const link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href;
|
const link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href;
|
||||||
tocHtml += `<a href="${rendition.book.path.directory + link}">${chapter.label}</a>`;
|
tocHtml += `<a href="${rendition.book.path.directory + link}">${chapter.label}</a>`;
|
||||||
tocHtml += '</li>';
|
tocHtml += '</li>';
|
||||||
|
@ -83,7 +84,7 @@ export default class TableOfContents {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
this._elem = elem;
|
this.elem = elem;
|
||||||
|
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
dialogHelper.open(elem);
|
dialogHelper.open(elem);
|
||||||
|
|
307
src/plugins/pdfPlayer/plugin.js
Normal file
307
src/plugins/pdfPlayer/plugin.js
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
import connectionManager from 'connectionManager';
|
||||||
|
import loading from 'loading';
|
||||||
|
import keyboardnavigation from 'keyboardnavigation';
|
||||||
|
import dialogHelper from 'dialogHelper';
|
||||||
|
import dom from 'dom';
|
||||||
|
import appRouter from 'appRouter';
|
||||||
|
import events from 'events';
|
||||||
|
import 'css!./style';
|
||||||
|
import 'material-icons';
|
||||||
|
import 'paper-icon-button-light';
|
||||||
|
|
||||||
|
export class PdfPlayer {
|
||||||
|
constructor() {
|
||||||
|
this.name = 'PDF Player';
|
||||||
|
this.type = 'mediaplayer';
|
||||||
|
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();
|
||||||
|
|
||||||
|
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 loading animation
|
||||||
|
loading.hide();
|
||||||
|
|
||||||
|
// cancel page render
|
||||||
|
this.cancellationToken = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
let key = keyboardnavigation.getKeyName(e);
|
||||||
|
|
||||||
|
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() {
|
||||||
|
let elem = this.mediaElement;
|
||||||
|
|
||||||
|
elem.addEventListener('close', this.onDialogClosed, {once: true});
|
||||||
|
elem.querySelector('.btnExit').addEventListener('click', this.onDialogClosed, {once: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
this.bindMediaElementEvents();
|
||||||
|
|
||||||
|
document.addEventListener('keyup', this.onWindowKeyUp);
|
||||||
|
document.addEventListener('touchstart', this.onTouchStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
unbindMediaElementEvents() {
|
||||||
|
let elem = this.mediaElement;
|
||||||
|
|
||||||
|
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">';
|
||||||
|
html += '<button is="paper-icon-button-light" class="autoSize btnExit" tabindex="-1"><i class="material-icons actionButtonIcon close"></i></button>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
elem.id = 'pdfPlayer';
|
||||||
|
elem.innerHTML = html;
|
||||||
|
|
||||||
|
dialogHelper.open(elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mediaElement = elem;
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentSrc(elem, options) {
|
||||||
|
let item = options.items[0];
|
||||||
|
|
||||||
|
this.item = item;
|
||||||
|
this.streamInfo = {
|
||||||
|
started: true,
|
||||||
|
ended: false,
|
||||||
|
mediaSource: {
|
||||||
|
Id: item.Id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let serverId = item.ServerId;
|
||||||
|
let apiClient = connectionManager.getApiClient(serverId);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
import('pdfjs').then(({default: pdfjs}) => {
|
||||||
|
let downloadHref = apiClient.getItemDownloadUrl(item.Id);
|
||||||
|
|
||||||
|
this.bindEvents();
|
||||||
|
pdfjs.GlobalWorkerOptions.workerSrc = appRouter.baseUrl() + '/libraries/pdf.worker.js';
|
||||||
|
|
||||||
|
let downloadTask = pdfjs.getDocument(downloadHref);
|
||||||
|
downloadTask.promise.then(book => {
|
||||||
|
if (this.cancellationToken) return;
|
||||||
|
this.book = book;
|
||||||
|
this.loaded = true;
|
||||||
|
|
||||||
|
const percentageTicks = options.startPositionTicks / 10000;
|
||||||
|
if (percentageTicks !== 0) {
|
||||||
|
this.loadPage(percentageTicks);
|
||||||
|
this.progress = percentageTicks;
|
||||||
|
} else {
|
||||||
|
this.loadPage(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next() {
|
||||||
|
if (this.progress === this.duration() - 1) return;
|
||||||
|
this.loadPage(this.progress + 2);
|
||||||
|
this.progress = this.progress + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
previous() {
|
||||||
|
if (this.progress === 0) return;
|
||||||
|
this.loadPage(this.progress);
|
||||||
|
this.progress = this.progress - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
let pages = [prefix + number];
|
||||||
|
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
|
||||||
|
for (let page of pages) {
|
||||||
|
if (!this.pages[page]) {
|
||||||
|
this.pages[page] = document.createElement('canvas');
|
||||||
|
this.renderPage(this.pages[page], parseInt(page.substr(4)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// show the requested page
|
||||||
|
this.replaceCanvas(this.pages[prefix + number], number);
|
||||||
|
|
||||||
|
// delete all pages outside the cache area
|
||||||
|
for (let page in this.pages) {
|
||||||
|
if (!pages.includes(page)) {
|
||||||
|
delete this.pages[page];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPage(canvas, number) {
|
||||||
|
this.book.getPage(number).then(page => {
|
||||||
|
events.trigger(this, 'timeupdate');
|
||||||
|
|
||||||
|
const original = page.getViewport({ scale: 1 });
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
const widthRatio = dom.getWindowSize().innerWidth / original.width;
|
||||||
|
const heightRatio = dom.getWindowSize().innerHeight / original.height;
|
||||||
|
const scale = Math.min(heightRatio, widthRatio);
|
||||||
|
const viewport = page.getViewport({ scale: scale });
|
||||||
|
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
var renderContext = {
|
||||||
|
canvasContext: context,
|
||||||
|
viewport: viewport
|
||||||
|
};
|
||||||
|
|
||||||
|
let renderTask = page.render(renderContext);
|
||||||
|
renderTask.promise.then(() => {
|
||||||
|
loading.hide();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
canPlayMediaType(mediaType) {
|
||||||
|
return (mediaType || '').toLowerCase() === 'book';
|
||||||
|
}
|
||||||
|
|
||||||
|
canPlayItem(item) {
|
||||||
|
if (item.Path && item.Path.endsWith('pdf')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PdfPlayer;
|
25
src/plugins/pdfPlayer/style.css
Normal file
25
src/plugins/pdfPlayer/style.css
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#pdfPlayer {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: none;
|
||||||
|
z-index: 100;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionButtons {
|
||||||
|
right: 0.5vh;
|
||||||
|
top: 0.5vh;
|
||||||
|
z-index: 1002;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionButtonIcon {
|
||||||
|
color: black;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
|
@ -503,6 +503,7 @@ function initClient() {
|
||||||
'flvjs',
|
'flvjs',
|
||||||
'jstree',
|
'jstree',
|
||||||
'epubjs',
|
'epubjs',
|
||||||
|
'pdfjs',
|
||||||
'jQuery',
|
'jQuery',
|
||||||
'hlsjs',
|
'hlsjs',
|
||||||
'howler',
|
'howler',
|
||||||
|
|
|
@ -12,7 +12,8 @@ const Assets = [
|
||||||
'libass-wasm/dist/js/subtitles-octopus-worker.wasm',
|
'libass-wasm/dist/js/subtitles-octopus-worker.wasm',
|
||||||
'libass-wasm/dist/js/subtitles-octopus-worker-legacy.js',
|
'libass-wasm/dist/js/subtitles-octopus-worker-legacy.js',
|
||||||
'libass-wasm/dist/js/subtitles-octopus-worker-legacy.data',
|
'libass-wasm/dist/js/subtitles-octopus-worker-legacy.data',
|
||||||
'libass-wasm/dist/js/subtitles-octopus-worker-legacy.js.mem'
|
'libass-wasm/dist/js/subtitles-octopus-worker-legacy.js.mem',
|
||||||
|
'pdfjs-dist/build/pdf.worker.js'
|
||||||
];
|
];
|
||||||
|
|
||||||
const LibarchiveWasm = [
|
const LibarchiveWasm = [
|
||||||
|
|
|
@ -8222,6 +8222,11 @@ pbkdf2@^3.0.3:
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
sha.js "^2.4.8"
|
sha.js "^2.4.8"
|
||||||
|
|
||||||
|
pdfjs-dist@^2.4.456:
|
||||||
|
version "2.4.456"
|
||||||
|
resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.4.456.tgz#0eaad2906cda866bbb393e79a0e5b4e68bd75520"
|
||||||
|
integrity sha512-yckJEHq3F48hcp6wStEpbN9McOj328Ib09UrBlGAKxvN2k+qYPN5iq6TH6jD1C0pso7zTep+g/CKsYgdrQd5QA==
|
||||||
|
|
||||||
pend@~1.2.0:
|
pend@~1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue