mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into unavailable-yt-video-trailer-bug-fix
This commit is contained in:
commit
6f61bee9d4
324 changed files with 8038 additions and 6596 deletions
|
@ -1,4 +1,5 @@
|
|||
/* eslint-disable indent */
|
||||
import ServerConnections from '../../components/ServerConnections';
|
||||
|
||||
class BackdropScreensaver {
|
||||
constructor() {
|
||||
|
@ -20,10 +21,10 @@ class BackdropScreensaver {
|
|||
Limit: 200
|
||||
};
|
||||
|
||||
const apiClient = window.connectionManager.currentApiClient();
|
||||
const apiClient = ServerConnections.currentApiClient();
|
||||
apiClient.getItems(apiClient.getCurrentUserId(), query).then((result) => {
|
||||
if (result.Items.length) {
|
||||
import('slideshow').then(({default: Slideshow}) => {
|
||||
import('../../components/slideshow/slideshow').then(({default: Slideshow}) => {
|
||||
const newSlideShow = new Slideshow({
|
||||
showTitle: true,
|
||||
cover: true,
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import browser from 'browser';
|
||||
import loading from 'loading';
|
||||
import keyboardnavigation from 'keyboardnavigation';
|
||||
import dialogHelper from 'dialogHelper';
|
||||
import dom from 'dom';
|
||||
import events from 'events';
|
||||
import 'css!./style';
|
||||
import 'material-icons';
|
||||
import 'paper-icon-button-light';
|
||||
|
||||
import loading from '../../components/loading/loading';
|
||||
import keyboardnavigation from '../../scripts/keyboardNavigation';
|
||||
import dialogHelper from '../../components/dialogHelper/dialogHelper';
|
||||
import '../../scripts/dom';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import './style.css';
|
||||
import 'material-design-icons-iconfont';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import ServerConnections from '../../components/ServerConnections';
|
||||
import TableOfContents from './tableOfContents';
|
||||
import browser from '../../scripts/browser';
|
||||
|
||||
export class BookPlayer {
|
||||
constructor() {
|
||||
|
@ -25,8 +25,9 @@ export class BookPlayer {
|
|||
}
|
||||
|
||||
play(options) {
|
||||
this._progress = 0;
|
||||
this._loaded = false;
|
||||
this.progress = 0;
|
||||
this.cancellationToken = false;
|
||||
this.loaded = false;
|
||||
|
||||
loading.show();
|
||||
const elem = this.createMediaElement();
|
||||
|
@ -36,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() {
|
||||
|
@ -96,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':
|
||||
|
@ -114,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();
|
||||
|
@ -130,7 +129,7 @@ export class BookPlayer {
|
|||
}
|
||||
|
||||
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});
|
||||
|
@ -147,11 +146,11 @@ export class BookPlayer {
|
|||
document.addEventListener('keyup', this.onWindowKeyUp);
|
||||
|
||||
// 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('keyup', this.onWindowKeyUp);
|
||||
}
|
||||
|
||||
unbindMediaElementEvents() {
|
||||
const elem = this._mediaElement;
|
||||
const elem = this.mediaElement;
|
||||
|
||||
elem.removeEventListener('close', this.onDialogClosed);
|
||||
elem.querySelector('#btnBookplayerExit').removeEventListener('click', this.onDialogClosed);
|
||||
|
@ -163,20 +162,20 @@ export class BookPlayer {
|
|||
}
|
||||
|
||||
unbindEvents() {
|
||||
if (this._mediaElement) {
|
||||
if (this.mediaElement) {
|
||||
this.unbindMediaElementEvents();
|
||||
}
|
||||
|
||||
document.removeEventListener('keyup', this.onWindowKeyUp);
|
||||
|
||||
if (this._rendition) {
|
||||
this._rendition.off('keyup', this.onWindowKeyUp);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,7 +190,7 @@ export class BookPlayer {
|
|||
}
|
||||
|
||||
createMediaElement() {
|
||||
let elem = this._mediaElement;
|
||||
let elem = this.mediaElement;
|
||||
if (elem) {
|
||||
return elem;
|
||||
}
|
||||
|
@ -207,8 +206,6 @@ export class BookPlayer {
|
|||
removeOnClose: true
|
||||
});
|
||||
|
||||
elem.id = 'bookPlayer';
|
||||
|
||||
let html = '';
|
||||
|
||||
if (browser.mobile) {
|
||||
|
@ -226,13 +223,13 @@ export class BookPlayer {
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -253,7 +250,7 @@ export class BookPlayer {
|
|||
|
||||
setCurrentSrc(elem, options) {
|
||||
const item = options.items[0];
|
||||
this._currentItem = item;
|
||||
this.item = item;
|
||||
this.streamInfo = {
|
||||
started: true,
|
||||
ended: false,
|
||||
|
@ -263,7 +260,7 @@ export class BookPlayer {
|
|||
};
|
||||
|
||||
const serverId = item.ServerId;
|
||||
const apiClient = window.connectionManager.getApiClient(serverId);
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
import('epubjs').then(({default: epubjs}) => {
|
||||
|
@ -271,13 +268,8 @@ export class BookPlayer {
|
|||
const book = epubjs(downloadHref, {openAs: 'epub'});
|
||||
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');
|
||||
|
@ -285,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) {
|
||||
|
@ -296,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);
|
||||
events.trigger(this, 'timeupdate');
|
||||
this.progress = book.locations.percentageFromCfi(locations.start.cfi);
|
||||
Events.trigger(this, 'timeupdate');
|
||||
});
|
||||
|
||||
loading.hide();
|
||||
|
||||
return resolve();
|
||||
});
|
||||
}, () => {
|
||||
|
@ -320,7 +309,7 @@ export class BookPlayer {
|
|||
}
|
||||
|
||||
canPlayItem(item) {
|
||||
if (item.Path && (item.Path.endsWith('epub'))) {
|
||||
if (item.Path && item.Path.endsWith('epub')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import dialogHelper from 'dialogHelper';
|
||||
import dialogHelper from '../../components/dialogHelper/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);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import events from 'events';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import ServerConnections from '../../components/ServerConnections';
|
||||
|
||||
// LinkParser
|
||||
//
|
||||
|
@ -221,8 +222,8 @@ function getCachedValue(key) {
|
|||
return null;
|
||||
}
|
||||
|
||||
events.on(window.connectionManager, 'localusersignedin', clearCache);
|
||||
events.on(window.connectionManager, 'localusersignedout', clearCache);
|
||||
Events.on(ServerConnections, 'localusersignedin', clearCache);
|
||||
Events.on(ServerConnections, 'localusersignedout', clearCache);
|
||||
|
||||
export default {
|
||||
getServerAddress: getServerAddress
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import appSettings from 'appSettings';
|
||||
import * as userSettings from 'userSettings';
|
||||
import playbackManager from 'playbackManager';
|
||||
import globalize from 'globalize';
|
||||
import events from 'events';
|
||||
import castSenderApiLoader from 'castSenderApiLoader';
|
||||
import appSettings from '../../scripts/settings/appSettings';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import { playbackManager } from '../../components/playback/playbackmanager';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import castSenderApiLoader from '../../components/castSenderApi';
|
||||
import ServerConnections from '../../components/ServerConnections';
|
||||
import alert from '../../components/alert';
|
||||
|
||||
// Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js
|
||||
|
||||
|
@ -167,7 +169,7 @@ class CastPlayer {
|
|||
alertText(globalize.translate('MessageChromecastConnectionError'), globalize.translate('HeaderError'));
|
||||
}, 300);
|
||||
} else if (message.type) {
|
||||
events.trigger(this, message.type, [message.data]);
|
||||
Events.trigger(this, message.type, [message.data]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,7 +238,7 @@ class CastPlayer {
|
|||
document.addEventListener('volumeupbutton', onVolumeUpKeyDown, false);
|
||||
document.addEventListener('volumedownbutton', onVolumeDownKeyDown, false);
|
||||
|
||||
events.trigger(this, 'connect');
|
||||
Events.trigger(this, 'connect');
|
||||
this.sendMessage({
|
||||
options: {},
|
||||
command: 'Identify'
|
||||
|
@ -324,11 +326,11 @@ class CastPlayer {
|
|||
|
||||
let apiClient;
|
||||
if (message.options && message.options.ServerId) {
|
||||
apiClient = window.connectionManager.getApiClient(message.options.ServerId);
|
||||
apiClient = ServerConnections.getApiClient(message.options.ServerId);
|
||||
} else if (message.options && message.options.items && message.options.items.length) {
|
||||
apiClient = window.connectionManager.getApiClient(message.options.items[0].ServerId);
|
||||
apiClient = ServerConnections.getApiClient(message.options.items[0].ServerId);
|
||||
} else {
|
||||
apiClient = window.connectionManager.currentApiClient();
|
||||
apiClient = ServerConnections.currentApiClient();
|
||||
}
|
||||
|
||||
message = Object.assign(message, {
|
||||
|
@ -439,11 +441,9 @@ class CastPlayer {
|
|||
}
|
||||
|
||||
function alertText(text, title) {
|
||||
import('alert').then(({default: alert}) => {
|
||||
alert({
|
||||
text: text,
|
||||
title: title
|
||||
});
|
||||
alert({
|
||||
text,
|
||||
title
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -495,11 +495,11 @@ function getItemsForPlayback(apiClient, query) {
|
|||
}
|
||||
|
||||
function bindEventForRelay(instance, eventName) {
|
||||
events.on(instance._castPlayer, eventName, function (e, data) {
|
||||
Events.on(instance._castPlayer, eventName, function (e, data) {
|
||||
console.debug('cc: ' + eventName);
|
||||
const state = instance.getPlayerStateInternal(data);
|
||||
|
||||
events.trigger(instance, eventName, [state]);
|
||||
Events.trigger(instance, eventName, [state]);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -514,7 +514,7 @@ function initializeChromecast() {
|
|||
}
|
||||
}));
|
||||
|
||||
events.on(instance._castPlayer, 'connect', function (e) {
|
||||
Events.on(instance._castPlayer, 'connect', function (e) {
|
||||
if (currentResolve) {
|
||||
sendConnectionResult(true);
|
||||
} else {
|
||||
|
@ -526,20 +526,20 @@ function initializeChromecast() {
|
|||
instance.lastPlayerData = null;
|
||||
});
|
||||
|
||||
events.on(instance._castPlayer, 'playbackstart', function (e, data) {
|
||||
Events.on(instance._castPlayer, 'playbackstart', function (e, data) {
|
||||
console.debug('cc: playbackstart');
|
||||
|
||||
instance._castPlayer.initializeCastPlayer();
|
||||
|
||||
const state = instance.getPlayerStateInternal(data);
|
||||
events.trigger(instance, 'playbackstart', [state]);
|
||||
Events.trigger(instance, 'playbackstart', [state]);
|
||||
});
|
||||
|
||||
events.on(instance._castPlayer, 'playbackstop', function (e, data) {
|
||||
Events.on(instance._castPlayer, 'playbackstop', function (e, data) {
|
||||
console.debug('cc: playbackstop');
|
||||
let state = instance.getPlayerStateInternal(data);
|
||||
|
||||
events.trigger(instance, 'playbackstop', [state]);
|
||||
Events.trigger(instance, 'playbackstop', [state]);
|
||||
|
||||
state = instance.lastPlayerData.PlayState || {};
|
||||
const volume = state.VolumeLevel || 0.5;
|
||||
|
@ -552,11 +552,11 @@ function initializeChromecast() {
|
|||
instance.lastPlayerData.PlayState.IsMuted = mute;
|
||||
});
|
||||
|
||||
events.on(instance._castPlayer, 'playbackprogress', function (e, data) {
|
||||
Events.on(instance._castPlayer, 'playbackprogress', function (e, data) {
|
||||
console.debug('cc: positionchange');
|
||||
const state = instance.getPlayerStateInternal(data);
|
||||
|
||||
events.trigger(instance, 'timeupdate', [state]);
|
||||
Events.trigger(instance, 'timeupdate', [state]);
|
||||
});
|
||||
|
||||
bindEventForRelay(instance, 'timeupdate');
|
||||
|
@ -566,11 +566,11 @@ function initializeChromecast() {
|
|||
bindEventForRelay(instance, 'repeatmodechange');
|
||||
bindEventForRelay(instance, 'shufflequeuemodechange');
|
||||
|
||||
events.on(instance._castPlayer, 'playstatechange', function (e, data) {
|
||||
Events.on(instance._castPlayer, 'playstatechange', function (e, data) {
|
||||
console.debug('cc: playstatechange');
|
||||
const state = instance.getPlayerStateInternal(data);
|
||||
|
||||
events.trigger(instance, 'pause', [state]);
|
||||
Events.trigger(instance, 'pause', [state]);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -664,7 +664,7 @@ class ChromecastPlayer {
|
|||
console.debug(JSON.stringify(data));
|
||||
|
||||
if (triggerStateChange) {
|
||||
events.trigger(this, 'statechange', [data]);
|
||||
Events.trigger(this, 'statechange', [data]);
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -672,7 +672,7 @@ class ChromecastPlayer {
|
|||
|
||||
playWithCommand(options, command) {
|
||||
if (!options.items) {
|
||||
const apiClient = window.connectionManager.getApiClient(options.serverId);
|
||||
const apiClient = ServerConnections.getApiClient(options.serverId);
|
||||
const instance = this;
|
||||
|
||||
return apiClient.getItem(apiClient.getCurrentUserId(), options.ids[0]).then(function (item) {
|
||||
|
@ -984,7 +984,7 @@ class ChromecastPlayer {
|
|||
}
|
||||
|
||||
shuffle(item) {
|
||||
const apiClient = window.connectionManager.getApiClient(item.ServerId);
|
||||
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
||||
const userId = apiClient.getCurrentUserId();
|
||||
|
||||
const instance = this;
|
||||
|
@ -997,7 +997,7 @@ class ChromecastPlayer {
|
|||
}
|
||||
|
||||
instantMix(item) {
|
||||
const apiClient = window.connectionManager.getApiClient(item.ServerId);
|
||||
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
||||
const userId = apiClient.getCurrentUserId();
|
||||
|
||||
const instance = this;
|
||||
|
@ -1035,7 +1035,7 @@ class ChromecastPlayer {
|
|||
}
|
||||
|
||||
const instance = this;
|
||||
const apiClient = window.connectionManager.getApiClient(options.serverId);
|
||||
const apiClient = ServerConnections.getApiClient(options.serverId);
|
||||
|
||||
return getItemsForPlayback(apiClient, {
|
||||
Ids: options.ids.join(',')
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import loading from 'loading';
|
||||
import dialogHelper from 'dialogHelper';
|
||||
import keyboardnavigation from 'keyboardnavigation';
|
||||
import appRouter from 'appRouter';
|
||||
import * as libarchive from 'libarchive';
|
||||
// eslint-disable-next-line import/named, import/namespace
|
||||
import { Archive } from 'libarchive.js';
|
||||
import loading from '../../components/loading/loading';
|
||||
import dialogHelper from '../../components/dialogHelper/dialogHelper';
|
||||
import keyboardnavigation from '../../scripts/keyboardNavigation';
|
||||
import { appRouter } from '../../components/appRouter';
|
||||
import ServerConnections from '../../components/ServerConnections';
|
||||
|
||||
export class ComicsPlayer {
|
||||
constructor() {
|
||||
|
@ -93,9 +95,9 @@ export class ComicsPlayer {
|
|||
loading.show();
|
||||
|
||||
const serverId = item.ServerId;
|
||||
const apiClient = window.connectionManager.getApiClient(serverId);
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
|
||||
libarchive.Archive.init({
|
||||
Archive.init({
|
||||
workerUrl: appRouter.baseUrl() + '/libraries/worker-bundle.js'
|
||||
});
|
||||
|
||||
|
@ -175,7 +177,7 @@ class ArchiveSource {
|
|||
}
|
||||
|
||||
const blob = await res.blob();
|
||||
this.archive = await libarchive.Archive.open(blob);
|
||||
this.archive = await Archive.open(blob);
|
||||
this.raw = await this.archive.getFilesArray();
|
||||
this.numberOfFiles = this.raw.length;
|
||||
await this.archive.extractFiles();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import globalize from 'globalize';
|
||||
import * as userSettings from 'userSettings';
|
||||
import appHost from 'apphost';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import { appHost } from '../../components/apphost';
|
||||
import alert from '../../components/alert';
|
||||
|
||||
// TODO: Replace with date-fns
|
||||
// https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php
|
||||
|
@ -26,13 +27,8 @@ function showMessage(text, userSettingsKey, appHostFeature) {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
userSettings.set(userSettingsKey, '1', false);
|
||||
|
||||
import('alert').then(({default: alert}) => {
|
||||
return alert(text).then(resolve, resolve);
|
||||
});
|
||||
});
|
||||
userSettings.set(userSettingsKey, '1', false);
|
||||
return alert(text).catch(() => { /* ignore exceptions */ });
|
||||
}
|
||||
|
||||
function showBlurayMessage() {
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import events from 'events';
|
||||
import browser from 'browser';
|
||||
import appHost from 'apphost';
|
||||
import * as htmlMediaHelper from 'htmlMediaHelper';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import browser from '../../scripts/browser';
|
||||
import { appHost } from '../../components/apphost';
|
||||
import * as htmlMediaHelper from '../../components/htmlMediaHelper';
|
||||
import profileBuilder from '../../scripts/browserDeviceProfile';
|
||||
|
||||
function getDefaultProfile() {
|
||||
return import('browserdeviceprofile').then(({ default: profileBuilder }) => {
|
||||
return profileBuilder({});
|
||||
});
|
||||
return profileBuilder({});
|
||||
}
|
||||
|
||||
let fadeTimeout;
|
||||
|
@ -51,7 +50,7 @@ function supportsFade() {
|
|||
}
|
||||
|
||||
function requireHlsPlayer(callback) {
|
||||
import('hlsjs').then(({ default: hls }) => {
|
||||
import('hls.js').then(({ default: hls }) => {
|
||||
window.Hls = hls;
|
||||
callback();
|
||||
});
|
||||
|
@ -68,7 +67,7 @@ function enableHlsPlayer(url, item, mediaSource, mediaType) {
|
|||
|
||||
// issue head request to get content type
|
||||
return new Promise(function (resolve, reject) {
|
||||
import('fetchHelper').then((fetchHelper) => {
|
||||
import('../../components/fetchhelper').then((fetchHelper) => {
|
||||
fetchHelper.ajax({
|
||||
url: url,
|
||||
type: 'HEAD'
|
||||
|
@ -251,14 +250,14 @@ class HtmlAudioPlayer {
|
|||
// Don't trigger events after user stop
|
||||
if (!self._isFadingOut) {
|
||||
self._currentTime = time;
|
||||
events.trigger(self, 'timeupdate');
|
||||
Events.trigger(self, 'timeupdate');
|
||||
}
|
||||
}
|
||||
|
||||
function onVolumeChange() {
|
||||
if (!self._isFadingOut) {
|
||||
htmlMediaHelper.saveVolume(this.volume);
|
||||
events.trigger(self, 'volumechange');
|
||||
Events.trigger(self, 'volumechange');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,19 +268,19 @@ class HtmlAudioPlayer {
|
|||
|
||||
htmlMediaHelper.seekOnPlaybackStart(self, e.target, self._currentPlayOptions.playerStartPositionTicks);
|
||||
}
|
||||
events.trigger(self, 'playing');
|
||||
Events.trigger(self, 'playing');
|
||||
}
|
||||
|
||||
function onPlay(e) {
|
||||
events.trigger(self, 'unpause');
|
||||
Events.trigger(self, 'unpause');
|
||||
}
|
||||
|
||||
function onPause() {
|
||||
events.trigger(self, 'pause');
|
||||
Events.trigger(self, 'pause');
|
||||
}
|
||||
|
||||
function onWaiting() {
|
||||
events.trigger(self, 'waiting');
|
||||
Events.trigger(self, 'waiting');
|
||||
}
|
||||
|
||||
function onError() {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import browser from 'browser';
|
||||
import events from 'events';
|
||||
import appHost from 'apphost';
|
||||
import loading from 'loading';
|
||||
import dom from 'dom';
|
||||
import playbackManager from 'playbackManager';
|
||||
import appRouter from 'appRouter';
|
||||
import browser from '../../scripts/browser';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import { appHost } from '../../components/apphost';
|
||||
import loading from '../../components/loading/loading';
|
||||
import dom from '../../scripts/dom';
|
||||
import { playbackManager } from '../../components/playback/playbackmanager';
|
||||
import { appRouter } from '../../components/appRouter';
|
||||
import {
|
||||
bindEventsToHlsPlayer,
|
||||
destroyHlsPlayer,
|
||||
|
@ -22,10 +22,12 @@ import {
|
|||
getSavedVolume,
|
||||
isValidDuration,
|
||||
getBufferedRanges
|
||||
} from 'htmlMediaHelper';
|
||||
import itemHelper from 'itemHelper';
|
||||
import screenfull from 'screenfull';
|
||||
import globalize from 'globalize';
|
||||
} from '../../components/htmlMediaHelper';
|
||||
import itemHelper from '../../components/itemHelper';
|
||||
import Screenfull from 'screenfull';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import ServerConnections from '../../components/ServerConnections';
|
||||
import profileBuilder from '../../scripts/browserDeviceProfile';
|
||||
|
||||
/* eslint-disable indent */
|
||||
|
||||
|
@ -85,7 +87,7 @@ function tryRemoveElement(elem) {
|
|||
}
|
||||
|
||||
function requireHlsPlayer(callback) {
|
||||
import('hlsjs').then(({default: hls}) => {
|
||||
import('hls.js').then(({default: hls}) => {
|
||||
window.Hls = hls;
|
||||
callback();
|
||||
});
|
||||
|
@ -139,9 +141,7 @@ function tryRemoveElement(elem) {
|
|||
}
|
||||
|
||||
function getDefaultProfile() {
|
||||
return import('browserdeviceprofile').then(({default: profileBuilder}) => {
|
||||
return profileBuilder({});
|
||||
});
|
||||
return profileBuilder({});
|
||||
}
|
||||
|
||||
export class HtmlVideoPlayer {
|
||||
|
@ -286,7 +286,7 @@ function tryRemoveElement(elem) {
|
|||
incrementFetchQueue() {
|
||||
if (this.#fetchQueue <= 0) {
|
||||
this.isFetching = true;
|
||||
events.trigger(this, 'beginFetch');
|
||||
Events.trigger(this, 'beginFetch');
|
||||
}
|
||||
|
||||
this.#fetchQueue++;
|
||||
|
@ -300,7 +300,7 @@ function tryRemoveElement(elem) {
|
|||
|
||||
if (this.#fetchQueue <= 0) {
|
||||
this.isFetching = false;
|
||||
events.trigger(this, 'endFetch');
|
||||
Events.trigger(this, 'endFetch');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,7 +323,7 @@ function tryRemoveElement(elem) {
|
|||
|
||||
console.debug(`prefetching hls playlist: ${hlsPlaylistUrl}`);
|
||||
|
||||
return window.connectionManager.getApiClient(item.ServerId).ajax({
|
||||
return ServerConnections.getApiClient(item.ServerId).ajax({
|
||||
|
||||
type: 'GET',
|
||||
url: hlsPlaylistUrl
|
||||
|
@ -362,7 +362,7 @@ function tryRemoveElement(elem) {
|
|||
* @private
|
||||
*/
|
||||
setSrcWithFlvJs(elem, options, url) {
|
||||
return import('flvjs').then(({default: flvjs}) => {
|
||||
return import('flv.js').then(({default: flvjs}) => {
|
||||
const flvPlayer = flvjs.createPlayer({
|
||||
type: 'flv',
|
||||
url: url
|
||||
|
@ -704,8 +704,8 @@ function tryRemoveElement(elem) {
|
|||
dlg.parentNode.removeChild(dlg);
|
||||
}
|
||||
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.exit();
|
||||
if (Screenfull.isEnabled) {
|
||||
Screenfull.exit();
|
||||
} else {
|
||||
// iOS Safari
|
||||
if (document.webkitIsFullScreen && document.webkitCancelFullscreen) {
|
||||
|
@ -754,7 +754,7 @@ function tryRemoveElement(elem) {
|
|||
this.updateSubtitleText(timeMs);
|
||||
}
|
||||
|
||||
events.trigger(this, 'timeupdate');
|
||||
Events.trigger(this, 'timeupdate');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -767,7 +767,7 @@ function tryRemoveElement(elem) {
|
|||
*/
|
||||
const elem = e.target;
|
||||
saveVolume(elem.volume);
|
||||
events.trigger(this, 'volumechange');
|
||||
Events.trigger(this, 'volumechange');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -826,14 +826,14 @@ function tryRemoveElement(elem) {
|
|||
this.onStartedAndNavigatedToOsd();
|
||||
}
|
||||
}
|
||||
events.trigger(this, 'playing');
|
||||
Events.trigger(this, 'playing');
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onPlay = () => {
|
||||
events.trigger(this, 'unpause');
|
||||
Events.trigger(this, 'unpause');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -859,25 +859,25 @@ function tryRemoveElement(elem) {
|
|||
* @private
|
||||
*/
|
||||
onClick = () => {
|
||||
events.trigger(this, 'click');
|
||||
Events.trigger(this, 'click');
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onDblClick = () => {
|
||||
events.trigger(this, 'dblclick');
|
||||
Events.trigger(this, 'dblclick');
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onPause = () => {
|
||||
events.trigger(this, 'pause');
|
||||
Events.trigger(this, 'pause');
|
||||
};
|
||||
|
||||
onWaiting() {
|
||||
events.trigger(this, 'waiting');
|
||||
Events.trigger(this, 'waiting');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1030,15 +1030,21 @@ function tryRemoveElement(elem) {
|
|||
* @private
|
||||
*/
|
||||
renderSsaAss(videoElement, track, item) {
|
||||
const avaliableFonts = [];
|
||||
const attachments = this._currentPlayOptions.mediaSource.MediaAttachments || [];
|
||||
const apiClient = window.connectionManager.getApiClient(item);
|
||||
attachments.map(function (i) {
|
||||
// embedded font url
|
||||
return avaliableFonts.push(i.DeliveryUrl);
|
||||
});
|
||||
const apiClient = ServerConnections.getApiClient(item);
|
||||
const fallbackFontList = apiClient.getUrl('/FallbackFont/Fonts', {
|
||||
api_key: apiClient.accessToken()
|
||||
});
|
||||
const htmlVideoPlayer = this;
|
||||
const options = {
|
||||
video: videoElement,
|
||||
subUrl: getTextTrackUrl(track, item),
|
||||
fonts: attachments.map(function (i) {
|
||||
return apiClient.getUrl(i.DeliveryUrl);
|
||||
}),
|
||||
fonts: avaliableFonts,
|
||||
workerUrl: `${appRouter.baseUrl()}/libraries/subtitles-octopus-worker.js`,
|
||||
legacyWorkerUrl: `${appRouter.baseUrl()}/libraries/subtitles-octopus-worker-legacy.js`,
|
||||
onError() {
|
||||
|
@ -1058,8 +1064,22 @@ function tryRemoveElement(elem) {
|
|||
resizeVariation: 0.2,
|
||||
renderAhead: 90
|
||||
};
|
||||
import('JavascriptSubtitlesOctopus').then(({default: SubtitlesOctopus}) => {
|
||||
this.#currentSubtitlesOctopus = new SubtitlesOctopus(options);
|
||||
import('libass-wasm').then(({default: SubtitlesOctopus}) => {
|
||||
apiClient.getNamedConfiguration('encoding').then(config => {
|
||||
if (config.EnableFallbackFont) {
|
||||
apiClient.getJSON(fallbackFontList).then((fontFiles = []) => {
|
||||
fontFiles.forEach(font => {
|
||||
const fontUrl = apiClient.getUrl(`/FallbackFont/Fonts/${font.Name}`, {
|
||||
api_key: apiClient.accessToken()
|
||||
});
|
||||
avaliableFonts.push(fontUrl);
|
||||
});
|
||||
this.#currentSubtitlesOctopus = new SubtitlesOctopus(options);
|
||||
});
|
||||
} else {
|
||||
this.#currentSubtitlesOctopus = new SubtitlesOctopus(options);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1114,7 +1134,7 @@ function tryRemoveElement(elem) {
|
|||
* @private
|
||||
*/
|
||||
setSubtitleAppearance(elem, innerElem) {
|
||||
Promise.all([import('userSettings'), import('subtitleAppearanceHelper')]).then(([userSettings, subtitleAppearanceHelper]) => {
|
||||
Promise.all([import('../../scripts/settings/userSettings'), import('../../components/subtitlesettings/subtitleappearancehelper')]).then(([userSettings, subtitleAppearanceHelper]) => {
|
||||
subtitleAppearanceHelper.applyStyles({
|
||||
text: innerElem,
|
||||
window: elem
|
||||
|
@ -1135,7 +1155,7 @@ function tryRemoveElement(elem) {
|
|||
* @private
|
||||
*/
|
||||
setCueAppearance() {
|
||||
Promise.all([import('userSettings'), import('subtitleAppearanceHelper')]).then(([userSettings, subtitleAppearanceHelper]) => {
|
||||
Promise.all([import('../../scripts/settings/userSettings'), import('../../components/subtitlesettings/subtitleappearancehelper')]).then(([userSettings, subtitleAppearanceHelper]) => {
|
||||
const elementId = `${this.id}-cuestyle`;
|
||||
|
||||
let styleElem = document.querySelector(`#${elementId}`);
|
||||
|
@ -1190,7 +1210,7 @@ function tryRemoveElement(elem) {
|
|||
|
||||
// download the track json
|
||||
this.fetchSubtitles(track, item).then(function (data) {
|
||||
import('userSettings').then((userSettings) => {
|
||||
import('../../scripts/settings/userSettings').then((userSettings) => {
|
||||
// show in ui
|
||||
console.debug(`downloaded ${data.TrackEvents.length} track events`);
|
||||
|
||||
|
@ -1282,7 +1302,7 @@ function tryRemoveElement(elem) {
|
|||
const dlg = document.querySelector('.videoPlayerContainer');
|
||||
|
||||
if (!dlg) {
|
||||
return import('css!./style').then(() => {
|
||||
return import('./style.css').then(() => {
|
||||
loading.show();
|
||||
|
||||
const dlg = document.createElement('div');
|
||||
|
@ -1561,7 +1581,7 @@ function tryRemoveElement(elem) {
|
|||
elem.style['-webkit-filter'] = `brightness(${cssValue})`;
|
||||
elem.style.filter = `brightness(${cssValue})`;
|
||||
elem.brightnessValue = val;
|
||||
events.trigger(this, 'brightnesschange');
|
||||
Events.trigger(this, 'brightnesschange');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import pluginManager from 'pluginManager';
|
||||
|
||||
export default function () {
|
||||
const self = this;
|
||||
|
||||
|
@ -128,7 +126,7 @@ export default function () {
|
|||
}
|
||||
|
||||
self.show = function () {
|
||||
import('css!' + pluginManager.mapPath(self, 'style.css')).then(() => {
|
||||
import('./style.css').then(() => {
|
||||
let elem = document.querySelector('.logoScreenSaver');
|
||||
|
||||
if (!elem) {
|
||||
|
|
306
src/plugins/pdfPlayer/plugin.js
Normal file
306
src/plugins/pdfPlayer/plugin.js
Normal file
|
@ -0,0 +1,306 @@
|
|||
import ServerConnections from '../../components/ServerConnections';
|
||||
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';
|
||||
import './style.css';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
|
||||
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 = ServerConnections.getApiClient(serverId);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
import('pdfjs-dist').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;
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import ServerConnections from '../../components/ServerConnections';
|
||||
|
||||
export default class PhotoPlayer {
|
||||
constructor() {
|
||||
|
@ -9,12 +10,12 @@ export default class PhotoPlayer {
|
|||
|
||||
play(options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
import('slideshow').then(({default: slideshow}) => {
|
||||
import('../../components/slideshow/slideshow').then(({default: Slideshow}) => {
|
||||
const index = options.startIndex || 0;
|
||||
|
||||
const apiClient = window.connectionManager.currentApiClient();
|
||||
const apiClient = ServerConnections.currentApiClient();
|
||||
apiClient.getCurrentUser().then(function(result) {
|
||||
const newSlideShow = new slideshow({
|
||||
const newSlideShow = new Slideshow({
|
||||
showTitle: false,
|
||||
cover: false,
|
||||
items: options.items,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import globalize from 'globalize';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import ServerConnections from '../../components/ServerConnections';
|
||||
import alert from '../../components/alert';
|
||||
|
||||
function showErrorMessage() {
|
||||
return import('alert').then(({default: alert}) => {
|
||||
return alert(globalize.translate('MessagePlayAccessRestricted'));
|
||||
});
|
||||
return alert(globalize.translate('MessagePlayAccessRestricted'));
|
||||
}
|
||||
|
||||
class PlayAccessValidation {
|
||||
|
@ -24,7 +24,7 @@ class PlayAccessValidation {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return window.connectionManager.getApiClient(serverId).getCurrentUser().then(function (user) {
|
||||
return ServerConnections.getApiClient(serverId).getCurrentUser().then(function (user) {
|
||||
if (user.Policy.EnableMediaPlayback) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import playbackManager from 'playbackManager';
|
||||
import events from 'events';
|
||||
import serverNotifications from 'serverNotifications';
|
||||
import { playbackManager } from '../../components/playback/playbackmanager';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import serverNotifications from '../../scripts/serverNotifications';
|
||||
import ServerConnections from '../../components/ServerConnections';
|
||||
|
||||
function getActivePlayerId() {
|
||||
const info = playbackManager.getPlayerInfo();
|
||||
|
@ -53,10 +54,10 @@ function getCurrentApiClient(instance) {
|
|||
const currentServerId = instance.currentServerId;
|
||||
|
||||
if (currentServerId) {
|
||||
return window.connectionManager.getApiClient(currentServerId);
|
||||
return ServerConnections.getApiClient(currentServerId);
|
||||
}
|
||||
|
||||
return window.connectionManager.currentApiClient();
|
||||
return ServerConnections.currentApiClient();
|
||||
}
|
||||
|
||||
function sendCommandByName(instance, name, options) {
|
||||
|
@ -104,7 +105,7 @@ function processUpdatedSessions(instance, sessions, apiClient) {
|
|||
instance.lastPlayerData = session;
|
||||
|
||||
for (let i = 0, length = eventNames.length; i < length; i++) {
|
||||
events.trigger(instance, eventNames[i], [session]);
|
||||
Events.trigger(instance, eventNames[i], [session]);
|
||||
}
|
||||
} else {
|
||||
instance.lastPlayerData = session;
|
||||
|
@ -186,7 +187,7 @@ class SessionPlayer {
|
|||
this.isLocalPlayer = false;
|
||||
this.id = 'remoteplayer';
|
||||
|
||||
events.on(serverNotifications, 'Sessions', function (e, apiClient, data) {
|
||||
Events.on(serverNotifications, 'Sessions', function (e, apiClient, data) {
|
||||
processUpdatedSessions(self, data, apiClient);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import events from 'events';
|
||||
import browser from 'browser';
|
||||
import appRouter from 'appRouter';
|
||||
import loading from 'loading';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import browser from '../../scripts/browser';
|
||||
import { appRouter } from '../../components/appRouter';
|
||||
import loading from '../../components/loading/loading';
|
||||
|
||||
/* globals YT */
|
||||
|
||||
|
@ -28,7 +28,7 @@ function createMediaElement(instance, options) {
|
|||
const dlg = document.querySelector('.youtubePlayerContainer');
|
||||
|
||||
if (!dlg) {
|
||||
import('css!./style').then(() => {
|
||||
import('./style.css').then(() => {
|
||||
loading.show();
|
||||
|
||||
const dlg = document.createElement('div');
|
||||
|
@ -88,7 +88,7 @@ function onEndedInternal(instance) {
|
|||
src: instance._currentSrc
|
||||
};
|
||||
|
||||
events.trigger(instance, 'stopped', [stopInfo]);
|
||||
Events.trigger(instance, 'stopped', [stopInfo]);
|
||||
|
||||
instance._currentSrc = null;
|
||||
if (instance.currentYoutubePlayer) {
|
||||
|
@ -103,7 +103,7 @@ function onPlayerReady(event) {
|
|||
}
|
||||
|
||||
function onTimeUpdate(e) {
|
||||
events.trigger(this, 'timeupdate');
|
||||
Events.trigger(this, 'timeupdate');
|
||||
}
|
||||
|
||||
function onPlaying(instance, playOptions, resolve) {
|
||||
|
@ -122,69 +122,65 @@ function onPlaying(instance, playOptions, resolve) {
|
|||
instance.videoDialog.classList.remove('onTop');
|
||||
}
|
||||
|
||||
import('loading').then(({default: loading}) => {
|
||||
loading.hide();
|
||||
});
|
||||
loading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function setCurrentSrc(instance, elem, options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
import('queryString').then(({default: queryString}) => {
|
||||
instance._currentSrc = options.url;
|
||||
const params = queryString.parse(options.url.split('?')[1]);
|
||||
// 3. This function creates an <iframe> (and YouTube player)
|
||||
// after the API code downloads.
|
||||
window.onYouTubeIframeAPIReady = function () {
|
||||
instance.currentYoutubePlayer = new YT.Player('player', {
|
||||
height: instance.videoDialog.offsetHeight,
|
||||
width: instance.videoDialog.offsetWidth,
|
||||
videoId: params.v,
|
||||
events: {
|
||||
'onReady': onPlayerReady,
|
||||
'onStateChange': function (event) {
|
||||
if (event.data === YT.PlayerState.PLAYING) {
|
||||
onPlaying(instance, options, resolve);
|
||||
} else if (event.data === YT.PlayerState.ENDED) {
|
||||
onEndedInternal(instance);
|
||||
} else if (event.data === YT.PlayerState.PAUSED) {
|
||||
events.trigger(instance, 'pause');
|
||||
}
|
||||
},
|
||||
'onError': (e) => reject(errorCodes[e.data] || '') // FIXME: default error code/text?
|
||||
instance._currentSrc = options.url;
|
||||
const params = new URLSearchParams(options.url.split('?')[1]); /* eslint-disable-line compat/compat */
|
||||
// 3. This function creates an <iframe> (and YouTube player)
|
||||
// after the API code downloads.
|
||||
window.onYouTubeIframeAPIReady = function () {
|
||||
instance.currentYoutubePlayer = new YT.Player('player', {
|
||||
height: instance.videoDialog.offsetHeight,
|
||||
width: instance.videoDialog.offsetWidth,
|
||||
videoId: params.get('v'),
|
||||
events: {
|
||||
'onReady': onPlayerReady,
|
||||
'onStateChange': function (event) {
|
||||
if (event.data === YT.PlayerState.PLAYING) {
|
||||
onPlaying(instance, options, resolve);
|
||||
} else if (event.data === YT.PlayerState.ENDED) {
|
||||
onEndedInternal(instance);
|
||||
} else if (event.data === YT.PlayerState.PAUSED) {
|
||||
Events.trigger(instance, 'pause');
|
||||
}
|
||||
},
|
||||
playerVars: {
|
||||
controls: 0,
|
||||
enablejsapi: 1,
|
||||
modestbranding: 1,
|
||||
rel: 0,
|
||||
showinfo: 0,
|
||||
fs: 0,
|
||||
playsinline: 1
|
||||
}
|
||||
});
|
||||
|
||||
let resizeListener = instance.resizeListener;
|
||||
if (resizeListener) {
|
||||
window.removeEventListener('resize', resizeListener);
|
||||
window.addEventListener('resize', resizeListener);
|
||||
} else {
|
||||
resizeListener = instance.resizeListener = onVideoResize.bind(instance);
|
||||
window.addEventListener('resize', resizeListener);
|
||||
'onError': (e) => reject(errorCodes[e.data] || '') // FIXME: default error code/text?
|
||||
},
|
||||
playerVars: {
|
||||
controls: 0,
|
||||
enablejsapi: 1,
|
||||
modestbranding: 1,
|
||||
rel: 0,
|
||||
showinfo: 0,
|
||||
fs: 0,
|
||||
playsinline: 1
|
||||
}
|
||||
window.removeEventListener('orientationChange', resizeListener);
|
||||
window.addEventListener('orientationChange', resizeListener);
|
||||
};
|
||||
});
|
||||
|
||||
if (!window.YT) {
|
||||
const tag = document.createElement('script');
|
||||
tag.src = 'https://www.youtube.com/iframe_api';
|
||||
const firstScriptTag = document.getElementsByTagName('script')[0];
|
||||
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
||||
let resizeListener = instance.resizeListener;
|
||||
if (resizeListener) {
|
||||
window.removeEventListener('resize', resizeListener);
|
||||
window.addEventListener('resize', resizeListener);
|
||||
} else {
|
||||
window.onYouTubeIframeAPIReady();
|
||||
resizeListener = instance.resizeListener = onVideoResize.bind(instance);
|
||||
window.addEventListener('resize', resizeListener);
|
||||
}
|
||||
});
|
||||
window.removeEventListener('orientationChange', resizeListener);
|
||||
window.addEventListener('orientationChange', resizeListener);
|
||||
};
|
||||
|
||||
if (!window.YT) {
|
||||
const tag = document.createElement('script');
|
||||
tag.src = 'https://www.youtube.com/iframe_api';
|
||||
const firstScriptTag = document.getElementsByTagName('script')[0];
|
||||
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
||||
} else {
|
||||
window.onYouTubeIframeAPIReady();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -287,7 +283,7 @@ class YoutubePlayer {
|
|||
|
||||
// This needs a delay before the youtube player will report the correct player state
|
||||
setTimeout(function () {
|
||||
events.trigger(instance, 'pause');
|
||||
Events.trigger(instance, 'pause');
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
@ -301,7 +297,7 @@ class YoutubePlayer {
|
|||
|
||||
// This needs a delay before the youtube player will report the correct player state
|
||||
setTimeout(function () {
|
||||
events.trigger(instance, 'unpause');
|
||||
Events.trigger(instance, 'unpause');
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue