mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into return-of-the-scrollbar
This commit is contained in:
commit
88b0d6d458
151 changed files with 4431 additions and 2598 deletions
|
@ -1,3 +1,4 @@
|
|||
import browser from 'browser';
|
||||
import loading from 'loading';
|
||||
import keyboardnavigation from 'keyboardnavigation';
|
||||
import dialogHelper from 'dialogHelper';
|
||||
|
@ -18,12 +19,15 @@ export class BookPlayer {
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
play(options) {
|
||||
this._progress = 0;
|
||||
this._loaded = false;
|
||||
this.progress = 0;
|
||||
this.cancellationToken = false;
|
||||
this.loaded = false;
|
||||
|
||||
loading.show();
|
||||
const elem = this.createMediaElement();
|
||||
|
@ -33,35 +37,35 @@ export class BookPlayer {
|
|||
stop() {
|
||||
this.unbindEvents();
|
||||
|
||||
const elem = this._mediaElement;
|
||||
const tocElement = this._tocElement;
|
||||
const rendition = this._rendition;
|
||||
const elem = this.mediaElement;
|
||||
const tocElement = this.tocElement;
|
||||
const rendition = this.rendition;
|
||||
|
||||
if (elem) {
|
||||
dialogHelper.close(elem);
|
||||
this._mediaElement = null;
|
||||
this.mediaElement = null;
|
||||
}
|
||||
|
||||
if (tocElement) {
|
||||
tocElement.destroy();
|
||||
this._tocElement = null;
|
||||
this.tocElement = null;
|
||||
}
|
||||
|
||||
if (rendition) {
|
||||
rendition.destroy();
|
||||
}
|
||||
|
||||
// Hide loader in case player was not fully loaded yet
|
||||
// hide loader in case player was not fully loaded yet
|
||||
loading.hide();
|
||||
this._cancellationToken.shouldCancel = true;
|
||||
this.cancellationToken = true;
|
||||
}
|
||||
|
||||
currentItem() {
|
||||
return this._currentItem;
|
||||
return this.item;
|
||||
}
|
||||
|
||||
currentTime() {
|
||||
return this._progress * 1000;
|
||||
return this.progress * 1000;
|
||||
}
|
||||
|
||||
duration() {
|
||||
|
@ -93,12 +97,10 @@ export class BookPlayer {
|
|||
|
||||
onWindowKeyUp(e) {
|
||||
const key = keyboardnavigation.getKeyName(e);
|
||||
|
||||
// TODO: depending on the event this can be the document or the rendition itself
|
||||
const rendition = this._rendition || this;
|
||||
const rendition = this.rendition;
|
||||
const book = rendition.book;
|
||||
|
||||
if (this._loaded === false) return;
|
||||
if (!this.loaded) return;
|
||||
switch (key) {
|
||||
case 'l':
|
||||
case 'ArrowRight':
|
||||
|
@ -111,9 +113,9 @@ export class BookPlayer {
|
|||
book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev();
|
||||
break;
|
||||
case 'Escape':
|
||||
if (this._tocElement) {
|
||||
if (this.tocElement) {
|
||||
// Close table of contents on ESC if it is open
|
||||
this._tocElement.destroy();
|
||||
this.tocElement.destroy();
|
||||
} else {
|
||||
// Otherwise stop the entire book player
|
||||
this.stop();
|
||||
|
@ -122,79 +124,73 @@ export class BookPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
onTouchStart(e) {
|
||||
// TODO: depending on the event this can be the document or the rendition itself
|
||||
const rendition = this._rendition || this;
|
||||
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
|
||||
// get the modulus of the touch event to account for the increased width
|
||||
if (!e.touches || e.touches.length === 0) return;
|
||||
|
||||
const touch = e.touches[0].clientX % dom.getWindowSize().innerWidth;
|
||||
if (touch < dom.getWindowSize().innerWidth / 2) {
|
||||
book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev();
|
||||
} else {
|
||||
book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next();
|
||||
}
|
||||
}
|
||||
|
||||
onDialogClosed() {
|
||||
this.stop();
|
||||
}
|
||||
|
||||
bindMediaElementEvents() {
|
||||
const elem = this._mediaElement;
|
||||
const elem = this.mediaElement;
|
||||
|
||||
elem.addEventListener('close', this.onDialogClosed, {once: true});
|
||||
elem.querySelector('.btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true});
|
||||
elem.querySelector('.btnBookplayerToc').addEventListener('click', this.openTableOfContents);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.bindMediaElementEvents();
|
||||
|
||||
document.addEventListener('keyup', this.onWindowKeyUp);
|
||||
document.addEventListener('touchstart', this.onTouchStart);
|
||||
|
||||
// FIXME: I don't really get why document keyup event is not triggered when epub is in focus
|
||||
this._rendition.on('keyup', this.onWindowKeyUp);
|
||||
this._rendition.on('touchstart', this.onTouchStart);
|
||||
this.rendition.on('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
|
||||
unbindMediaElementEvents() {
|
||||
const elem = this._mediaElement;
|
||||
const elem = this.mediaElement;
|
||||
|
||||
elem.removeEventListener('close', this.onDialogClosed);
|
||||
elem.querySelector('.btnBookplayerExit').removeEventListener('click', this.onDialogClosed);
|
||||
elem.querySelector('.btnBookplayerToc').removeEventListener('click', this.openTableOfContents);
|
||||
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() {
|
||||
if (this._mediaElement) {
|
||||
if (this.mediaElement) {
|
||||
this.unbindMediaElementEvents();
|
||||
}
|
||||
|
||||
document.removeEventListener('keyup', this.onWindowKeyUp);
|
||||
document.removeEventListener('touchstart', this.onTouchStart);
|
||||
|
||||
if (this._rendition) {
|
||||
this._rendition.off('keyup', this.onWindowKeyUp);
|
||||
this._rendition.off('touchstart', this.onTouchStart);
|
||||
if (this.rendition) {
|
||||
this.rendition.off('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
}
|
||||
|
||||
openTableOfContents() {
|
||||
if (this._loaded) {
|
||||
this._tocElement = new TableOfContents(this);
|
||||
if (this.loaded) {
|
||||
this.tocElement = new TableOfContents(this);
|
||||
}
|
||||
}
|
||||
|
||||
prevChapter(e) {
|
||||
this._rendition.prev();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
nextChapter(e) {
|
||||
this._rendition.next();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
createMediaElement() {
|
||||
let elem = this._mediaElement;
|
||||
let elem = this.mediaElement;
|
||||
if (elem) {
|
||||
return elem;
|
||||
}
|
||||
|
@ -210,29 +206,51 @@ export class BookPlayer {
|
|||
removeOnClose: true
|
||||
});
|
||||
|
||||
elem.id = 'bookPlayer';
|
||||
|
||||
let html = '';
|
||||
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>';
|
||||
|
||||
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 class="topLeftActionButtons">';
|
||||
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>';
|
||||
|
||||
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>';
|
||||
}
|
||||
|
||||
elem.id = 'bookPlayer';
|
||||
elem.innerHTML = html;
|
||||
|
||||
dialogHelper.open(elem);
|
||||
}
|
||||
|
||||
this._mediaElement = elem;
|
||||
|
||||
this.mediaElement = elem;
|
||||
return elem;
|
||||
}
|
||||
|
||||
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];
|
||||
this._currentItem = item;
|
||||
this.item = item;
|
||||
this.streamInfo = {
|
||||
started: true,
|
||||
ended: false,
|
||||
|
@ -248,15 +266,10 @@ export class BookPlayer {
|
|||
import('epubjs').then(({default: epubjs}) => {
|
||||
const downloadHref = apiClient.getItemDownloadUrl(item.Id);
|
||||
const book = epubjs(downloadHref, {openAs: 'epub'});
|
||||
const rendition = book.renderTo(elem, {width: '100%', height: '97%'});
|
||||
const rendition = this.render('viewer', book);
|
||||
|
||||
this._currentSrc = downloadHref;
|
||||
this._rendition = rendition;
|
||||
const cancellationToken = {
|
||||
shouldCancel: false
|
||||
};
|
||||
|
||||
this._cancellationToken = cancellationToken;
|
||||
this.currentSrc = downloadHref;
|
||||
this.rendition = rendition;
|
||||
|
||||
return rendition.display().then(() => {
|
||||
const epubElem = document.querySelector('.epub-container');
|
||||
|
@ -264,10 +277,8 @@ export class BookPlayer {
|
|||
|
||||
this.bindEvents();
|
||||
|
||||
return this._rendition.book.locations.generate(1024).then(async () => {
|
||||
if (cancellationToken.shouldCancel) {
|
||||
return reject();
|
||||
}
|
||||
return this.rendition.book.locations.generate(1024).then(async () => {
|
||||
if (this.cancellationToken) reject();
|
||||
|
||||
const percentageTicks = options.startPositionTicks / 10000000;
|
||||
if (percentageTicks !== 0.0) {
|
||||
|
@ -275,15 +286,14 @@ export class BookPlayer {
|
|||
await rendition.display(resumeLocation);
|
||||
}
|
||||
|
||||
this._loaded = true;
|
||||
this.loaded = true;
|
||||
epubElem.style.display = 'block';
|
||||
rendition.on('relocated', (locations) => {
|
||||
this._progress = book.locations.percentageFromCfi(locations.start.cfi);
|
||||
this.progress = book.locations.percentageFromCfi(locations.start.cfi);
|
||||
events.trigger(this, 'timeupdate');
|
||||
});
|
||||
|
||||
loading.hide();
|
||||
|
||||
return resolve();
|
||||
});
|
||||
}, () => {
|
||||
|
@ -299,7 +309,7 @@ export class BookPlayer {
|
|||
}
|
||||
|
||||
canPlayItem(item) {
|
||||
if (item.Path && (item.Path.endsWith('epub'))) {
|
||||
if (item.Path && item.Path.endsWith('epub')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,18 +7,20 @@
|
|||
background: #fff;
|
||||
}
|
||||
|
||||
.topRightActionButtons {
|
||||
right: 0.5vh;
|
||||
.topButtons {
|
||||
top: 0.5vh;
|
||||
z-index: 1002;
|
||||
position: absolute;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.topLeftActionButtons {
|
||||
left: 0.5vh;
|
||||
top: 0.5vh;
|
||||
z-index: 1002;
|
||||
position: absolute;
|
||||
#btnBookplayerToc {
|
||||
float: left;
|
||||
margin-left: 2vw;
|
||||
}
|
||||
|
||||
#btnBookplayerExit {
|
||||
float: right;
|
||||
margin-right: 2vw;
|
||||
}
|
||||
|
||||
.bookplayerButtonIcon {
|
||||
|
@ -37,3 +39,31 @@
|
|||
.bookplayerErrorMsg {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#viewer {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#btnBookplayerPrev {
|
||||
margin: 0.5vh 0.5vh;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#btnBookplayerNext {
|
||||
margin: 0.5vh 0.5vh;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.top-button {
|
||||
margin: 0.5vh 2em;
|
||||
}
|
||||
|
||||
.bottom-button {
|
||||
margin: 2em 0.5vh;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ import dialogHelper from 'dialogHelper';
|
|||
|
||||
export default class TableOfContents {
|
||||
constructor(bookPlayer) {
|
||||
this._bookPlayer = bookPlayer;
|
||||
this._rendition = bookPlayer._rendition;
|
||||
this.bookPlayer = bookPlayer;
|
||||
this.rendition = bookPlayer.rendition;
|
||||
|
||||
this.onDialogClosed = this.onDialogClosed.bind(this);
|
||||
|
||||
|
@ -11,24 +11,24 @@ export default class TableOfContents {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
const elem = this._elem;
|
||||
const elem = this.elem;
|
||||
if (elem) {
|
||||
this.unbindEvents();
|
||||
dialogHelper.close(elem);
|
||||
}
|
||||
|
||||
this._bookPlayer._tocElement = null;
|
||||
this.bookPlayer.tocElement = null;
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const elem = this._elem;
|
||||
const elem = this.elem;
|
||||
|
||||
elem.addEventListener('close', this.onDialogClosed, {once: true});
|
||||
elem.querySelector('.btnBookplayerTocClose').addEventListener('click', this.onDialogClosed, {once: true});
|
||||
}
|
||||
|
||||
unbindEvents() {
|
||||
const elem = this._elem;
|
||||
const elem = this.elem;
|
||||
|
||||
elem.removeEventListener('close', this.onDialogClosed);
|
||||
elem.querySelector('.btnBookplayerTocClose').removeEventListener('click', this.onDialogClosed);
|
||||
|
@ -52,7 +52,7 @@ export default class TableOfContents {
|
|||
}
|
||||
|
||||
createMediaElement() {
|
||||
const rendition = this._rendition;
|
||||
const rendition = this.rendition;
|
||||
|
||||
const elem = dialogHelper.createDialog({
|
||||
size: 'small',
|
||||
|
@ -68,7 +68,8 @@ export default class TableOfContents {
|
|||
tocHtml += '<ul class="toc">';
|
||||
rendition.book.navigation.forEach((chapter) => {
|
||||
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;
|
||||
tocHtml += `<a href="${rendition.book.path.directory + link}">${chapter.label}</a>`;
|
||||
tocHtml += '</li>';
|
||||
|
@ -83,7 +84,7 @@ export default class TableOfContents {
|
|||
this.destroy();
|
||||
});
|
||||
|
||||
this._elem = elem;
|
||||
this.elem = elem;
|
||||
|
||||
this.bindEvents();
|
||||
dialogHelper.open(elem);
|
||||
|
|
|
@ -103,7 +103,7 @@ export class ComicsPlayer {
|
|||
const downloadUrl = apiClient.getItemDownloadUrl(item.Id);
|
||||
const archiveSource = new ArchiveSource(downloadUrl);
|
||||
|
||||
var instance = this;
|
||||
const instance = this;
|
||||
import('swiper').then(({default: Swiper}) => {
|
||||
archiveSource.load().then(() => {
|
||||
loading.hide();
|
||||
|
|
|
@ -114,7 +114,6 @@ function tryRemoveElement(elem) {
|
|||
return new Promise(resolve => {
|
||||
const duration = 240;
|
||||
elem.style.animation = `htmlvideoplayer-zoomin ${duration}ms ease-in normal`;
|
||||
hidePrePlaybackPage();
|
||||
dom.addEventListener(elem, dom.whichAnimationEvent(), resolve, {
|
||||
once: true
|
||||
});
|
||||
|
@ -1329,17 +1328,24 @@ function tryRemoveElement(elem) {
|
|||
this.#videoDialog = dlg;
|
||||
this.#mediaElement = videoElement;
|
||||
|
||||
if (options.fullscreen) {
|
||||
hidePrePlaybackPage();
|
||||
}
|
||||
|
||||
// don't animate on smart tv's, too slow
|
||||
if (options.fullscreen && browser.supportsCssAnimation() && !browser.slow) {
|
||||
return zoomIn(dlg).then(function () {
|
||||
return videoElement;
|
||||
});
|
||||
} else {
|
||||
hidePrePlaybackPage();
|
||||
return videoElement;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (options.fullscreen) {
|
||||
hidePrePlaybackPage();
|
||||
}
|
||||
|
||||
return Promise.resolve(dlg.querySelector('video'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,11 @@
|
|||
order: -1;
|
||||
}
|
||||
|
||||
video::-webkit-media-controls {
|
||||
/* Controls are enabled for devices that don't support autoplay. They will be hidden when playback starts.
|
||||
In Tizen 2.3 (and probably other old web engines), subtitles are located under '-webkit-media-controls' tree.
|
||||
Therefore, we hide controls only if they are enabled.
|
||||
*/
|
||||
video[controls]::-webkit-media-controls {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
|
306
src/plugins/pdfPlayer/plugin.js
Normal file
306
src/plugins/pdfPlayer/plugin.js
Normal file
|
@ -0,0 +1,306 @@
|
|||
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();
|
||||
|
||||
const elem = this.createMediaElement();
|
||||
return this.setCurrentSrc(elem, options);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.unbindEvents();
|
||||
|
||||
const 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) {
|
||||
const 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() {
|
||||
const 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() {
|
||||
const 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) {
|
||||
const item = options.items[0];
|
||||
|
||||
this.item = item;
|
||||
this.streamInfo = {
|
||||
started: true,
|
||||
ended: false,
|
||||
mediaSource: {
|
||||
Id: item.Id
|
||||
}
|
||||
};
|
||||
|
||||
const serverId = item.ServerId;
|
||||
const apiClient = window.connectionManager.getApiClient(serverId);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
import('pdfjs').then(({default: pdfjs}) => {
|
||||
const downloadHref = apiClient.getItemDownloadUrl(item.Id);
|
||||
|
||||
this.bindEvents();
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = appRouter.baseUrl() + '/libraries/pdf.worker.js';
|
||||
|
||||
const 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
|
||||
const 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 (const 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 (const 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;
|
||||
const renderContext = {
|
||||
canvasContext: context,
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
const 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;
|
||||
}
|
|
@ -10,11 +10,11 @@ export default class PhotoPlayer {
|
|||
play(options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
import('slideshow').then(({default: slideshow}) => {
|
||||
var index = options.startIndex || 0;
|
||||
const index = options.startIndex || 0;
|
||||
|
||||
var apiClient = window.connectionManager.currentApiClient();
|
||||
const apiClient = window.connectionManager.currentApiClient();
|
||||
apiClient.getCurrentUser().then(function(result) {
|
||||
var newSlideShow = new slideshow({
|
||||
const newSlideShow = new slideshow({
|
||||
showTitle: false,
|
||||
cover: false,
|
||||
items: options.items,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue