1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge branch 'master' into segment-deletion

This commit is contained in:
Dominik 2023-06-15 20:30:56 +02:00 committed by GitHub
commit 128184cc72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
497 changed files with 70077 additions and 54756 deletions

View file

@ -1,51 +1,51 @@
/* eslint-disable indent */
import ServerConnections from '../../components/ServerConnections';
import { PluginType } from '../../types/plugin.ts';
class BackdropScreensaver {
constructor() {
this.name = 'Backdrop ScreenSaver';
this.type = 'screensaver';
this.type = PluginType.Screensaver;
this.id = 'backdropscreensaver';
this.supportsAnonymous = false;
}
show() {
const query = {
ImageTypes: 'Backdrop',
EnableImageTypes: 'Backdrop',
IncludeItemTypes: 'Movie,Series,MusicArtist',
SortBy: 'Random',
Recursive: true,
Fields: 'Taglines',
ImageTypeLimit: 1,
StartIndex: 0,
Limit: 200
};
show() {
const query = {
ImageTypes: 'Backdrop',
EnableImageTypes: 'Backdrop',
IncludeItemTypes: 'Movie,Series,MusicArtist',
SortBy: 'Random',
Recursive: true,
Fields: 'Taglines',
ImageTypeLimit: 1,
StartIndex: 0,
Limit: 200
};
const apiClient = ServerConnections.currentApiClient();
apiClient.getItems(apiClient.getCurrentUserId(), query).then((result) => {
if (result.Items.length) {
import('../../components/slideshow/slideshow').then(({default: Slideshow}) => {
const newSlideShow = new Slideshow({
showTitle: true,
cover: true,
items: result.Items
});
const apiClient = ServerConnections.currentApiClient();
apiClient.getItems(apiClient.getCurrentUserId(), query).then((result) => {
if (result.Items.length) {
import('../../components/slideshow/slideshow').then(({ default: Slideshow }) => {
const newSlideShow = new Slideshow({
showTitle: true,
cover: true,
items: result.Items
});
newSlideShow.show();
this.currentSlideshow = newSlideShow;
}).catch(console.error);
}
});
}
hide() {
if (this.currentSlideshow) {
this.currentSlideshow.hide();
this.currentSlideshow = null;
newSlideShow.show();
this.currentSlideshow = newSlideShow;
}).catch(console.error);
}
return Promise.resolve();
}
});
}
/* eslint-enable indent */
hide() {
if (this.currentSlideshow) {
this.currentSlideshow.hide();
this.currentSlideshow = null;
}
return Promise.resolve();
}
}
export default BackdropScreensaver;

View file

@ -9,6 +9,7 @@ import TableOfContents from './tableOfContents';
import dom from '../../scripts/dom';
import { translateHtml } from '../../scripts/globalize';
import * as userSettings from '../../scripts/settings/userSettings';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
import '../../elements/emby-button/paper-icon-button-light';
@ -16,15 +17,31 @@ import '../../elements/emby-button/paper-icon-button-light';
import html from './template.html';
import './style.scss';
const THEMES = {
'dark': { 'body': { 'color': '#d8dadc', 'background': '#000', 'font-size': 'medium' } },
'sepia': { 'body': { 'color': '#d8a262', 'background': '#000', 'font-size': 'medium' } },
'light': { 'body': { 'color': '#000', 'background': '#fff', 'font-size': 'medium' } }
};
const THEME_ORDER = ['dark', 'sepia', 'light'];
const FONT_SIZES = ['x-small', 'small', 'medium', 'large', 'x-large'];
export class BookPlayer {
constructor() {
this.name = 'Book Player';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'bookplayer';
this.priority = 1;
if (!userSettings.theme() || userSettings.theme() === 'dark') {
this.theme = 'dark';
} else {
this.theme = 'light';
}
this.fontSize = 'medium';
this.onDialogClosed = this.onDialogClosed.bind(this);
this.openTableOfContents = this.openTableOfContents.bind(this);
this.rotateTheme = this.rotateTheme.bind(this);
this.increaseFontSize = this.increaseFontSize.bind(this);
this.decreaseFontSize = this.decreaseFontSize.bind(this);
this.previous = this.previous.bind(this);
this.next = this.next.bind(this);
this.onWindowKeyUp = this.onWindowKeyUp.bind(this);
@ -163,6 +180,9 @@ export class BookPlayer {
elem.querySelector('#btnBookplayerExit').addEventListener('click', this.onDialogClosed, { once: true });
elem.querySelector('#btnBookplayerToc').addEventListener('click', this.openTableOfContents);
elem.querySelector('#btnBookplayerFullscreen').addEventListener('click', this.toggleFullscreen);
elem.querySelector('#btnBookplayerRotateTheme').addEventListener('click', this.rotateTheme);
elem.querySelector('#btnBookplayerIncreaseFontSize').addEventListener('click', this.increaseFontSize);
elem.querySelector('#btnBookplayerDecreaseFontSize').addEventListener('click', this.decreaseFontSize);
elem.querySelector('#btnBookplayerPrev')?.addEventListener('click', this.previous);
elem.querySelector('#btnBookplayerNext')?.addEventListener('click', this.next);
}
@ -183,6 +203,9 @@ export class BookPlayer {
elem.querySelector('#btnBookplayerExit').removeEventListener('click', this.onDialogClosed);
elem.querySelector('#btnBookplayerToc').removeEventListener('click', this.openTableOfContents);
elem.querySelector('#btnBookplayerFullscreen').removeEventListener('click', this.toggleFullscreen);
elem.querySelector('#btnBookplayerRotateTheme').removeEventListener('click', this.rotateTheme);
elem.querySelector('#btnBookplayerIncreaseFontSize').removeEventListener('click', this.increaseFontSize);
elem.querySelector('#btnBookplayerDecreaseFontSize').removeEventListener('click', this.decreaseFontSize);
elem.querySelector('#btnBookplayerPrev')?.removeEventListener('click', this.previous);
elem.querySelector('#btnBookplayerNext')?.removeEventListener('click', this.next);
}
@ -213,6 +236,31 @@ export class BookPlayer {
}
}
rotateTheme() {
if (this.loaded) {
const newTheme = THEME_ORDER[(THEME_ORDER.indexOf(this.theme) + 1) % THEME_ORDER.length];
this.rendition.themes.register('default', THEMES[newTheme]);
this.rendition.themes.update('default');
this.theme = newTheme;
}
}
increaseFontSize() {
if (this.loaded && this.fontSize !== FONT_SIZES[FONT_SIZES.length - 1]) {
const newFontSize = FONT_SIZES[(FONT_SIZES.indexOf(this.fontSize) + 1)];
this.rendition.themes.fontSize(newFontSize);
this.fontSize = newFontSize;
}
}
decreaseFontSize() {
if (this.loaded && this.fontSize !== FONT_SIZES[0]) {
const newFontSize = FONT_SIZES[(FONT_SIZES.indexOf(this.fontSize) - 1)];
this.rendition.themes.fontSize(newFontSize);
this.fontSize = newFontSize;
}
}
previous(e) {
e?.preventDefault();
if (this.rendition) {
@ -295,11 +343,8 @@ export class BookPlayer {
this.currentSrc = downloadHref;
this.rendition = rendition;
rendition.themes.register('dark', { 'body': { 'color': '#fff' } });
if (userSettings.theme(undefined) === 'dark' || userSettings.theme(undefined) === null) {
rendition.themes.select('dark');
}
rendition.themes.register('default', THEMES[this.theme]);
rendition.themes.select('default');
return rendition.display().then(() => {
const epubElem = document.querySelector('.epub-container');

View file

@ -23,8 +23,8 @@ export default class TableOfContents {
bindEvents() {
const elem = this.elem;
elem.addEventListener('close', this.onDialogClosed, {once: true});
elem.querySelector('.btnBookplayerTocClose').addEventListener('click', this.onDialogClosed, {once: true});
elem.addEventListener('close', this.onDialogClosed, { once: true });
elem.querySelector('.btnBookplayerTocClose').addEventListener('click', this.onDialogClosed, { once: true });
}
unbindEvents() {

View file

@ -11,6 +11,15 @@
<button is="paper-icon-button-light" id="btnBookplayerExit" class="autoSize bookplayerButton hide-mouse-idle-tv" tabindex="-1">
<span class="material-icons bookplayerButtonIcon close" aria-hidden="true"></span>
</button>
<button is="paper-icon-button-light" id="btnBookplayerRotateTheme" class="autoSize bookplayerButton hide-mouse-idle-tv" tabindex="-1">
<span class="material-icons bookplayerButtonIcon remove_red_eye" aria-hidden="true"></span>
</button>
<button is="paper-icon-button-light" id="btnBookplayerDecreaseFontSize" class="autoSize bookplayerButton hide-mouse-idle-tv" tabindex="-1">
<span class="material-icons bookplayerButtonIcon text_decrease" aria-hidden="true"></span>
</button>
<button is="paper-icon-button-light" id="btnBookplayerIncreaseFontSize" class="autoSize bookplayerButton hide-mouse-idle-tv" tabindex="-1">
<span class="material-icons bookplayerButtonIcon text_increase" aria-hidden="true"></span>
</button>
<button is="paper-icon-button-light" id="btnBookplayerFullscreen" class="autoSize bookplayerButton hide-mouse-idle-tv" tabindex="-1">
<span class="material-icons bookplayerButtonIcon fullscreen" aria-hidden="true"></span>
</button>

View file

@ -5,7 +5,9 @@ import globalize from '../../scripts/globalize';
import castSenderApiLoader from './castSenderApi';
import ServerConnections from '../../components/ServerConnections';
import alert from '../../components/alert';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
// Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js
@ -481,7 +483,7 @@ function getItemsForPlayback(apiClient, query) {
query.ExcludeLocationTypes = 'Virtual';
query.EnableTotalRecordCount = false;
return apiClient.getItems(userId, query);
return getItems(apiClient, userId, query);
}
}
@ -569,7 +571,7 @@ class ChromecastPlayer {
constructor() {
// playbackManager needs this
this.name = PlayerName;
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'chromecast';
this.isLocalPlayer = false;
this.lastPlayerData = {};
@ -622,6 +624,7 @@ class ChromecastPlayer {
isLocalPlayer: false,
appName: PlayerName,
deviceName: appName,
deviceType: 'cast',
supportedCommands: [
'VolumeUp',
'VolumeDown',
@ -683,7 +686,7 @@ class ChromecastPlayer {
}
seek(position) {
position = parseInt(position);
position = parseInt(position, 10);
position = position / 10000000;

View file

@ -3,16 +3,17 @@ 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 { appRouter } from '../../components/router/appRouter';
import ServerConnections from '../../components/ServerConnections';
import * as userSettings from '../../scripts/settings/userSettings';
import { PluginType } from '../../types/plugin.ts';
import './style.scss';
export class ComicsPlayer {
constructor() {
this.name = 'Comics Player';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'comicsplayer';
this.priority = 1;
this.imageMap = new Map();

View file

@ -2,6 +2,7 @@ import globalize from '../../scripts/globalize';
import * as userSettings from '../../scripts/settings/userSettings';
import { appHost } from '../../components/apphost';
import alert from '../../components/alert';
import { PluginType } from '../../types/plugin.ts';
// TODO: Replace with date-fns
// https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php
@ -46,7 +47,7 @@ function showIsoMessage() {
class ExpirementalPlaybackWarnings {
constructor() {
this.name = 'Experimental playback warnings';
this.type = 'preplayintercept';
this.type = PluginType.PreplayIntercept;
this.id = 'expirementalplaybackwarnings';
}

View file

@ -3,6 +3,7 @@ import { appHost } from '../../components/apphost';
import * as htmlMediaHelper from '../../components/htmlMediaHelper';
import profileBuilder from '../../scripts/browserDeviceProfile';
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
function getDefaultProfile() {
@ -47,7 +48,10 @@ function supportsFade() {
}
function requireHlsPlayer(callback) {
import('hls.js').then(({ default: hls }) => {
import('hls.js/dist/hls.js').then(({ default: hls }) => {
hls.DefaultConfig.lowLatencyMode = false;
hls.DefaultConfig.backBufferLength = Infinity;
hls.DefaultConfig.liveBackBufferLength = 90;
window.Hls = hls;
callback();
});
@ -85,7 +89,7 @@ class HtmlAudioPlayer {
const self = this;
self.name = 'Html Audio Player';
self.type = 'mediaplayer';
self.type = PluginType.MediaPlayer;
self.id = 'htmlaudioplayer';
// Let any players created by plugins take priority
@ -97,6 +101,7 @@ class HtmlAudioPlayer {
self._currentTime = null;
const elem = createMediaElement();
return setCurrentSrc(elem, options);
};
@ -106,6 +111,17 @@ class HtmlAudioPlayer {
let val = options.url;
console.debug('playing url: ' + val);
import('../../scripts/settings/userSettings').then((userSettings) => {
if (userSettings.enableAudioNormalization() && options.item.LUFS != null) {
const dbGain = -18 - options.item.LUFS;
self.gainNode.gain.value = Math.pow(10, (dbGain / 20));
} else {
self.gainNode.gain.value = 1;
}
console.debug('gain:' + self.gainNode.gain.value);
}).catch((err) => {
console.error('Failed to add/change gainNode', err);
});
// Convert to seconds
const seconds = (options.playerStartPositionTicks || 0) / 10000000;
@ -241,9 +257,29 @@ class HtmlAudioPlayer {
self._mediaElement = elem;
addGainElement(elem);
return elem;
}
function addGainElement(elem) {
try {
const AudioContext = window.AudioContext || window.webkitAudioContext; /* eslint-disable-line compat/compat */
const audioCtx = new AudioContext();
const source = audioCtx.createMediaElementSource(elem);
const gainNode = audioCtx.createGain();
source.connect(gainNode);
gainNode.connect(audioCtx.destination);
self.gainNode = gainNode;
} catch (e) {
console.error('Web Audio API is not supported in this browser', e);
}
}
function onEnded() {
htmlMediaHelper.onEndedInternal(self, this, onError);
}

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@
z-index: 1000;
}
.videoPlayerContainer .libassjs-canvas-parent {
.videoPlayerContainer .JASSUB {
order: -1;
}
@ -65,13 +65,22 @@ video[controls]::-webkit-media-controls {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
display: flex;
flex-direction: column;
align-items: center;
}
.videoSubtitlesInner {
max-width: 70%;
background-color: rgba(0, 0, 0, 0.8);
margin: auto;
display: inline-block;
}
.videoSecondarySubtitlesInner {
max-width: 70%;
background-color: rgba(0, 0, 0, 0.8);
min-height: 0 !important;
margin-top: 0.5em !important;
margin-bottom: 0.5em !important;
}
@keyframes htmlvideoplayer-zoomin {

View file

@ -1,8 +1,11 @@
import { PluginType } from '../../types/plugin.ts';
import { randomInt } from '../../utils/number.ts';
export default function () {
const self = this;
self.name = 'Logo ScreenSaver';
self.type = 'screensaver';
self.type = PluginType.Screensaver;
self.id = 'logoscreensaver';
self.supportsAnonymous = true;
@ -23,16 +26,12 @@ export default function () {
const elem = document.querySelector('.logoScreenSaverImage');
if (elem && elem.animate) {
const random = getRandomInt(0, animations.length - 1);
const random = randomInt(0, animations.length - 1);
animations[random](elem, 1);
}
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function bounceInLeft(elem, iterations) {
const keyframes = [
{ transform: 'translate3d(-3000px, 0, 0)', opacity: '0', offset: 0 },

View file

@ -3,7 +3,8 @@ 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 { appRouter } from '../../components/router/appRouter';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
import './style.scss';
@ -12,7 +13,7 @@ import '../../elements/emby-button/paper-icon-button-light';
export class PdfPlayer {
constructor() {
this.name = 'PDF Player';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'pdfplayer';
this.priority = 1;
@ -261,7 +262,7 @@ export class PdfPlayer {
for (const page of pages) {
if (!this.pages[page]) {
this.pages[page] = document.createElement('canvas');
this.renderPage(this.pages[page], parseInt(page.slice(4)));
this.renderPage(this.pages[page], parseInt(page.slice(4), 10));
}
}
@ -278,16 +279,22 @@ export class PdfPlayer {
renderPage(canvas, number) {
this.book.getPage(number).then(page => {
const original = page.getViewport({ scale: 1 });
const width = dom.getWindowSize().innerWidth;
const height = dom.getWindowSize().innerHeight;
const scale = Math.ceil(window.devicePixelRatio || 1);
const viewport = page.getViewport({ scale });
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;
if (width < height) {
canvas.style.width = '100%';
canvas.style.height = 'auto';
} else {
canvas.style.height = '100%';
canvas.style.width = 'auto';
}
const renderContext = {
canvasContext: context,
viewport: viewport

View file

@ -1,16 +1,17 @@
import ServerConnections from '../../components/ServerConnections';
import { PluginType } from '../../types/plugin.ts';
export default class PhotoPlayer {
constructor() {
this.name = 'Photo Player';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'photoplayer';
this.priority = 1;
}
play(options) {
return new Promise(function (resolve) {
import('../../components/slideshow/slideshow').then(({default: Slideshow}) => {
import('../../components/slideshow/slideshow').then(({ default: Slideshow }) => {
const index = options.startIndex || 0;
const apiClient = ServerConnections.currentApiClient();

View file

@ -1,6 +1,7 @@
import globalize from '../../scripts/globalize';
import ServerConnections from '../../components/ServerConnections';
import alert from '../../components/alert';
import { PluginType } from '../../types/plugin.ts';
function showErrorMessage() {
return alert(globalize.translate('MessagePlayAccessRestricted'));
@ -9,7 +10,7 @@ function showErrorMessage() {
class PlayAccessValidation {
constructor() {
this.name = 'Playback validation';
this.type = 'preplayintercept';
this.type = PluginType.PreplayIntercept;
this.id = 'playaccessvalidation';
this.order = -2;
}

View file

@ -1,6 +1,7 @@
import { playbackManager } from '../../components/playback/playbackmanager';
import serverNotifications from '../../scripts/serverNotifications';
import ServerConnections from '../../components/ServerConnections';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
function getActivePlayerId() {
@ -181,7 +182,7 @@ class SessionPlayer {
const self = this;
this.name = 'Remote Control';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.isLocalPlayer = false;
this.id = 'remoteplayer';

View file

@ -4,6 +4,7 @@
*/
import Events from '../../../utils/events.ts';
import { getItems } from '../../../utils/jellyfin-apiclient/getItems.ts';
/**
* Constants
@ -88,7 +89,7 @@ export function getItemsForPlayback(apiClient, query) {
query.EnableTotalRecordCount = false;
query.CollapseBoxSetItems = false;
return apiClient.getItems(apiClient.getCurrentUserId(), query);
return getItems(apiClient, apiClient.getCurrentUserId(), query);
}
}

View file

@ -71,7 +71,7 @@ class Manager {
/**
* Update active ApiClient.
* @param {Object} apiClient The ApiClient.
* @param {ApiClient|undefined} apiClient The ApiClient.
*/
updateApiClient(apiClient) {
if (!apiClient) {
@ -254,7 +254,7 @@ class Manager {
if (typeof cmd.When === 'string') {
cmd.When = new Date(cmd.When);
cmd.EmittedAt = new Date(cmd.EmittedAt);
cmd.PositionTicks = cmd.PositionTicks ? parseInt(cmd.PositionTicks) : null;
cmd.PositionTicks = cmd.PositionTicks ? parseInt(cmd.PositionTicks, 10) : null;
}
if (!this.isSyncPlayEnabled()) {

View file

@ -133,9 +133,9 @@ class PlaybackCore {
*/
async sendBufferingRequest(isBuffering = true) {
const playerWrapper = this.manager.getPlayerWrapper();
const currentPosition = (playerWrapper.currentTimeAsync
? await playerWrapper.currentTimeAsync()
: playerWrapper.currentTime());
const currentPosition = (playerWrapper.currentTimeAsync ?
await playerWrapper.currentTimeAsync() :
playerWrapper.currentTime());
const currentPositionTicks = Math.round(currentPosition * Helper.TicksPerMillisecond);
const isPlaying = playerWrapper.isPlaying();
@ -172,11 +172,11 @@ class PlaybackCore {
*/
async applyCommand(command) {
// Check if duplicate.
if (this.lastCommand &&
this.lastCommand.When.getTime() === command.When.getTime() &&
this.lastCommand.PositionTicks === command.PositionTicks &&
this.lastCommand.Command === command.Command &&
this.lastCommand.PlaylistItemId === command.PlaylistItemId
if (this.lastCommand
&& this.lastCommand.When.getTime() === command.When.getTime()
&& this.lastCommand.PositionTicks === command.PositionTicks
&& this.lastCommand.Command === command.Command
&& this.lastCommand.PlaylistItemId === command.PlaylistItemId
) {
// Duplicate command found, check playback state and correct if needed.
console.debug('SyncPlay applyCommand: duplicate command received!', command);
@ -192,9 +192,9 @@ class PlaybackCore {
} else {
// Check if playback state matches requested command.
const playerWrapper = this.manager.getPlayerWrapper();
const currentPositionTicks = Math.round((playerWrapper.currentTimeAsync
? await playerWrapper.currentTimeAsync()
: playerWrapper.currentTime()) * Helper.TicksPerMillisecond);
const currentPositionTicks = Math.round((playerWrapper.currentTimeAsync ?
await playerWrapper.currentTimeAsync() :
playerWrapper.currentTime()) * Helper.TicksPerMillisecond);
const isPlaying = playerWrapper.isPlaying();
switch (command.Command) {
@ -279,9 +279,9 @@ class PlaybackCore {
const playAtTimeLocal = this.timeSyncCore.remoteDateToLocal(playAtTime);
const playerWrapper = this.manager.getPlayerWrapper();
const currentPositionTicks = (playerWrapper.currentTimeAsync
? await playerWrapper.currentTimeAsync()
: playerWrapper.currentTime()) * Helper.TicksPerMillisecond;
const currentPositionTicks = (playerWrapper.currentTimeAsync ?
await playerWrapper.currentTimeAsync() :
playerWrapper.currentTime()) * Helper.TicksPerMillisecond;
if (playAtTimeLocal > currentTime) {
const playTimeout = playAtTimeLocal - currentTime;

View file

@ -174,9 +174,9 @@ class QueueCore {
const currentTime = new Date();
const now = this.manager.timeSyncCore.localDateToRemote(currentTime);
const currentPosition = (playerWrapper.currentTimeAsync
? await playerWrapper.currentTimeAsync()
: playerWrapper.currentTime());
const currentPosition = (playerWrapper.currentTimeAsync ?
await playerWrapper.currentTimeAsync() :
playerWrapper.currentTime());
const currentPositionTicks = Math.round(currentPosition * Helper.TicksPerMillisecond);
const isPlaying = playerWrapper.isPlaying();

View file

@ -5,21 +5,25 @@ import SyncPlay from './core';
import SyncPlayNoActivePlayer from './ui/players/NoActivePlayer';
import SyncPlayHtmlVideoPlayer from './ui/players/HtmlVideoPlayer';
import SyncPlayHtmlAudioPlayer from './ui/players/HtmlAudioPlayer';
import { Plugin, PluginType } from '../../types/plugin';
class SyncPlayPlugin {
class SyncPlayPlugin implements Plugin {
name: string;
id: string;
type: string;
priority: number;
instance: typeof SyncPlay;
constructor() {
this.name = 'SyncPlay Plugin';
this.id = 'syncplay';
// NOTE: This should probably be a "mediaplayer" so the playback manager can handle playback logic, but
// SyncPlay needs refactored so it does not have an independent playback manager.
this.type = 'syncplay';
this.type = PluginType.SyncPlay;
this.priority = 1;
this.instance = SyncPlay;
this.init();
}

View file

@ -1,11 +1,12 @@
import SyncPlay from '../core';
import SyncPlaySettingsEditor from './settings/SettingsEditor';
import loading from '../../../components/loading/loading';
import toast from '../../../components/toast/toast';
import actionsheet from '../../../components/actionSheet/actionSheet';
import globalize from '../../../scripts/globalize';
import playbackPermissionManager from './playbackPermissionManager';
import { pluginManager } from '../../../components/pluginManager';
import ServerConnections from '../../../components/ServerConnections';
import { PluginType } from '../../../types/plugin.ts';
import Events from '../../../utils/events.ts';
import './groupSelectionMenu.scss';
@ -17,8 +18,22 @@ class GroupSelectionMenu {
constructor() {
// Register to SyncPlay events.
this.syncPlayEnabled = false;
Events.on(SyncPlay.Manager, 'enabled', (e, enabled) => {
this.syncPlayEnabled = enabled;
this.SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
if (this.SyncPlay) {
Events.on(this.SyncPlay.Manager, 'enabled', (_event, enabled) => {
this.syncPlayEnabled = enabled;
});
}
Events.on(pluginManager, 'registered', (_event0, plugin) => {
if (plugin.type === PluginType.SyncPlay) {
this.SyncPlay = plugin.instance;
Events.on(plugin.instance.Manager, 'enabled', (_event1, enabled) => {
this.syncPlayEnabled = enabled;
});
}
});
}
@ -103,10 +118,11 @@ class GroupSelectionMenu {
* @param {Object} apiClient - ApiClient.
*/
showLeaveGroupSelection(button, user, apiClient) {
const groupInfo = SyncPlay.Manager.getGroupInfo();
const groupInfo = this.SyncPlay?.Manager.getGroupInfo();
const menuItems = [];
if (!SyncPlay.Manager.isPlaylistEmpty() && !SyncPlay.Manager.isPlaybackActive()) {
if (!this.SyncPlay?.Manager.isPlaylistEmpty()
&& !this.SyncPlay?.Manager.isPlaybackActive()) {
menuItems.push({
name: globalize.translate('LabelSyncPlayResumePlayback'),
icon: 'play_circle_filled',
@ -114,7 +130,7 @@ class GroupSelectionMenu {
selected: false,
secondaryText: globalize.translate('LabelSyncPlayResumePlaybackDescription')
});
} else if (SyncPlay.Manager.isPlaybackActive()) {
} else if (this.SyncPlay?.Manager.isPlaybackActive()) {
menuItems.push({
name: globalize.translate('LabelSyncPlayHaltPlayback'),
icon: 'pause_circle_filled',
@ -149,15 +165,15 @@ class GroupSelectionMenu {
border: true
};
actionsheet.show(menuOptions).then(function (id) {
actionsheet.show(menuOptions).then((id) => {
if (id == 'resume-playback') {
SyncPlay.Manager.resumeGroupPlayback(apiClient);
this.SyncPlay?.Manager.resumeGroupPlayback(apiClient);
} else if (id == 'halt-playback') {
SyncPlay.Manager.haltGroupPlayback(apiClient);
this.SyncPlay?.Manager.haltGroupPlayback(apiClient);
} else if (id == 'leave-group') {
apiClient.leaveSyncPlayGroup();
} else if (id == 'settings') {
new SyncPlaySettingsEditor(apiClient, SyncPlay.Manager.getTimeSyncCore(), { groupInfo: groupInfo })
new SyncPlaySettingsEditor(apiClient, this.SyncPlay?.Manager.getTimeSyncCore(), { groupInfo: groupInfo })
.embed()
.catch(error => {
if (error) {

View file

@ -3,13 +3,14 @@
* @module components/syncPlay/settings/SettingsEditor
*/
import SyncPlay from '../../core';
import { setSetting } from '../../core/Settings';
import dialogHelper from '../../../../components/dialogHelper/dialogHelper';
import layoutManager from '../../../../components/layoutManager';
import { pluginManager } from '../../../../components/pluginManager';
import loading from '../../../../components/loading/loading';
import toast from '../../../../components/toast/toast';
import globalize from '../../../../scripts/globalize';
import { PluginType } from '../../../../types/plugin.ts';
import Events from '../../../../utils/events.ts';
import 'material-design-icons-iconfont';
@ -36,6 +37,7 @@ class SettingsEditor {
this.apiClient = apiClient;
this.timeSyncCore = timeSyncCore;
this.options = options;
this.SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
}
async embed() {
@ -95,14 +97,14 @@ class SettingsEditor {
async initEditor() {
const { context } = this;
context.querySelector('#txtExtraTimeOffset').value = SyncPlay.Manager.timeSyncCore.extraTimeOffset;
context.querySelector('#chkSyncCorrection').checked = SyncPlay.Manager.playbackCore.enableSyncCorrection;
context.querySelector('#txtMinDelaySpeedToSync').value = SyncPlay.Manager.playbackCore.minDelaySpeedToSync;
context.querySelector('#txtMaxDelaySpeedToSync').value = SyncPlay.Manager.playbackCore.maxDelaySpeedToSync;
context.querySelector('#txtSpeedToSyncDuration').value = SyncPlay.Manager.playbackCore.speedToSyncDuration;
context.querySelector('#txtMinDelaySkipToSync').value = SyncPlay.Manager.playbackCore.minDelaySkipToSync;
context.querySelector('#chkSpeedToSync').checked = SyncPlay.Manager.playbackCore.useSpeedToSync;
context.querySelector('#chkSkipToSync').checked = SyncPlay.Manager.playbackCore.useSkipToSync;
context.querySelector('#txtExtraTimeOffset').value = this.SyncPlay?.Manager.timeSyncCore.extraTimeOffset;
context.querySelector('#chkSyncCorrection').checked = this.SyncPlay?.Manager.playbackCore.enableSyncCorrection;
context.querySelector('#txtMinDelaySpeedToSync').value = this.SyncPlay?.Manager.playbackCore.minDelaySpeedToSync;
context.querySelector('#txtMaxDelaySpeedToSync').value = this.SyncPlay?.Manager.playbackCore.maxDelaySpeedToSync;
context.querySelector('#txtSpeedToSyncDuration').value = this.SyncPlay?.Manager.playbackCore.speedToSyncDuration;
context.querySelector('#txtMinDelaySkipToSync').value = this.SyncPlay?.Manager.playbackCore.minDelaySkipToSync;
context.querySelector('#chkSpeedToSync').checked = this.SyncPlay?.Manager.playbackCore.useSpeedToSync;
context.querySelector('#chkSkipToSync').checked = this.SyncPlay?.Manager.playbackCore.useSkipToSync;
}
onSubmit() {
@ -139,7 +141,7 @@ class SettingsEditor {
setSetting('useSpeedToSync', useSpeedToSync);
setSetting('useSkipToSync', useSkipToSync);
Events.trigger(SyncPlay.Manager, 'settings-update');
Events.trigger(this.SyncPlay?.Manager, 'settings-update');
}
}

View file

@ -1,7 +1,8 @@
import browser from '../../scripts/browser';
import { appRouter } from '../../components/appRouter';
import { appRouter } from '../../components/router/appRouter';
import loading from '../../components/loading/loading';
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
/* globals YT */
@ -197,7 +198,7 @@ function setCurrentSrc(instance, elem, options) {
class YoutubePlayer {
constructor() {
this.name = 'Youtube Player';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'youtubeplayer';
// Let any players created by plugins take priority