mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-06-24 21:25:23 +00:00
Receivers: Added image viewer UI
This commit is contained in:
parent
023f1054e1
commit
7e4b5485ea
6 changed files with 419 additions and 122 deletions
|
@ -22,11 +22,13 @@ export class Timer {
|
||||||
private delay: number;
|
private delay: number;
|
||||||
private startTime: number;
|
private startTime: number;
|
||||||
private remainingTime: number;
|
private remainingTime: number;
|
||||||
|
public started: boolean;
|
||||||
|
|
||||||
constructor(callback: () => void, delay: number, autoStart: boolean = true) {
|
constructor(callback: () => void, delay: number, autoStart: boolean = true) {
|
||||||
this.handle = null;
|
this.handle = null;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.delay = delay;
|
this.delay = delay;
|
||||||
|
this.started = false;
|
||||||
|
|
||||||
if (autoStart) {
|
if (autoStart) {
|
||||||
this.start();
|
this.start();
|
||||||
|
@ -40,6 +42,7 @@ export class Timer {
|
||||||
window.clearTimeout(this.handle);
|
window.clearTimeout(this.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.started = true;
|
||||||
this.startTime = Date.now();
|
this.startTime = Date.now();
|
||||||
this.remainingTime = null;
|
this.remainingTime = null;
|
||||||
this.handle = window.setTimeout(this.callback, this.delay);
|
this.handle = window.setTimeout(this.callback, this.delay);
|
||||||
|
@ -64,6 +67,7 @@ export class Timer {
|
||||||
window.clearTimeout(this.handle);
|
window.clearTimeout(this.handle);
|
||||||
this.handle = null;
|
this.handle = null;
|
||||||
this.remainingTime = null;
|
this.remainingTime = null;
|
||||||
|
this.started = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { EventMessage, EventType, KeyEvent, MediaItem, MediaItemEvent, PlaylistContent, PlayMessage, SeekMessage, SetPlaylistItemMessage, SetSpeedMessage, SetVolumeMessage } from 'common/Packets';
|
import { EventMessage, EventType, GenericMediaMetadata, KeyEvent, MediaItem, MediaItemEvent, MetadataType, PlaybackState, PlaylistContent, PlayMessage, SeekMessage, SetPlaylistItemMessage, SetSpeedMessage, SetVolumeMessage } from 'common/Packets';
|
||||||
import { mediaItemFromPlayMessage, playMessageFromMediaItem } from 'common/UtilityFrontend';
|
import { mediaItemFromPlayMessage, playMessageFromMediaItem, Timer } from 'common/UtilityFrontend';
|
||||||
import { supportedImageTypes } from 'common/MimeTypes';
|
import { supportedImageTypes } from 'common/MimeTypes';
|
||||||
import * as connectionMonitor from 'common/ConnectionMonitor';
|
import * as connectionMonitor from 'common/ConnectionMonitor';
|
||||||
import { toast, ToastIcon } from 'common/components/Toast';
|
import { toast, ToastIcon } from 'common/components/Toast';
|
||||||
|
@ -10,18 +10,53 @@ import {
|
||||||
|
|
||||||
const logger = window.targetAPI.logger;
|
const logger = window.targetAPI.logger;
|
||||||
|
|
||||||
const idleBackground = document.getElementById('video-player');
|
// HTML elements
|
||||||
const idleIcon = document.getElementById('title-icon');
|
const idleBackground = document.getElementById('idleBackground');
|
||||||
// todo: add callbacks for on-load events for image and generic content viewer
|
const idleIcon = document.getElementById('titleIcon');
|
||||||
const loadingSpinner = document.getElementById('loading-spinner');
|
const loadingSpinner = document.getElementById('loadingSpinner');
|
||||||
const imageViewer = document.getElementById('viewer-image') as HTMLImageElement;
|
const imageViewer = document.getElementById('viewerImage') as HTMLImageElement;
|
||||||
const genericViewer = document.getElementById('viewer-generic') as HTMLIFrameElement;
|
const genericViewer = document.getElementById('viewerGeneric') as HTMLIFrameElement;
|
||||||
|
|
||||||
|
const mediaTitle = document.getElementById("mediaTitle");
|
||||||
|
const playerControls = document.getElementById("controls");
|
||||||
|
|
||||||
|
const playerCtrlPlayPrevious = document.getElementById("playPrevious");
|
||||||
|
const playerCtrlAction = document.getElementById("action");
|
||||||
|
const playerCtrlPlaylistLength = document.getElementById("playlistLength");
|
||||||
|
const playerCtrlPlayNext = document.getElementById("playNext");
|
||||||
|
|
||||||
let cachedPlaylist: PlaylistContent = null;
|
let cachedPlaylist: PlaylistContent = null;
|
||||||
let cachedPlayMediaItem: MediaItem = null;
|
let cachedPlayMediaItem: MediaItem = null;
|
||||||
let showDurationTimeout: number = null;
|
|
||||||
let playlistIndex = 0;
|
let playlistIndex = 0;
|
||||||
let isMediaItem = false;
|
let isMediaItem = false;
|
||||||
let playItemCached = false;
|
let playItemCached = false;
|
||||||
|
let imageViewerPlaybackState: PlaybackState = PlaybackState.Idle;
|
||||||
|
|
||||||
|
let uiHideTimer = new Timer(() => {
|
||||||
|
uiVisible = false;
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
|
||||||
|
}, 3000);
|
||||||
|
let loadingTimer = new Timer(() => { loadingSpinner.style.display = 'block'; }, 100, false);
|
||||||
|
|
||||||
|
let showDurationTimer = new Timer(() => {
|
||||||
|
if (playlistIndex < cachedPlaylist.items.length - 1) {
|
||||||
|
setPlaylistItem(playlistIndex + 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.info('End of playlist');
|
||||||
|
imageViewer.style.display = 'none';
|
||||||
|
imageViewer.src = '';
|
||||||
|
|
||||||
|
genericViewer.style.display = 'none';
|
||||||
|
genericViewer.src = '';
|
||||||
|
|
||||||
|
idleBackground.style.display = 'block';
|
||||||
|
idleIcon.style.display = 'block';
|
||||||
|
|
||||||
|
playerCtrlAction.setAttribute("class", "play iconSize");
|
||||||
|
imageViewerPlaybackState = PlaybackState.Idle;
|
||||||
|
}
|
||||||
|
}, 0, false);
|
||||||
|
|
||||||
function onPlay(_event, value: PlayMessage) {
|
function onPlay(_event, value: PlayMessage) {
|
||||||
if (!playItemCached) {
|
if (!playItemCached) {
|
||||||
|
@ -31,12 +66,21 @@ function onPlay(_event, value: PlayMessage) {
|
||||||
window.targetAPI.sendEvent(new EventMessage(Date.now(), new MediaItemEvent(EventType.MediaItemChange, cachedPlayMediaItem)));
|
window.targetAPI.sendEvent(new EventMessage(Date.now(), new MediaItemEvent(EventType.MediaItemChange, cachedPlayMediaItem)));
|
||||||
logger.info('Media playback changed:', cachedPlayMediaItem);
|
logger.info('Media playback changed:', cachedPlayMediaItem);
|
||||||
playItemCached = false;
|
playItemCached = false;
|
||||||
|
showDurationTimer.stop();
|
||||||
|
|
||||||
window.targetAPI.sendEvent(new EventMessage(Date.now(), new MediaItemEvent(EventType.MediaItemChange, cachedPlayMediaItem)));
|
window.targetAPI.sendEvent(new EventMessage(Date.now(), new MediaItemEvent(EventType.MediaItemChange, cachedPlayMediaItem)));
|
||||||
const src = value.url ? value.url : value.content;
|
const src = value.url ? value.url : value.content;
|
||||||
|
|
||||||
|
loadingTimer.start();
|
||||||
if (src && value.container && supportedImageTypes.find(v => v === value.container.toLocaleLowerCase()) && imageViewer) {
|
if (src && value.container && supportedImageTypes.find(v => v === value.container.toLocaleLowerCase()) && imageViewer) {
|
||||||
logger.info('Loading image viewer');
|
logger.info('Loading image viewer');
|
||||||
|
imageViewer.onload = (ev) => {
|
||||||
|
loadingTimer.stop();
|
||||||
|
loadingSpinner.style.display = 'none';
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.Load);
|
||||||
|
};
|
||||||
|
|
||||||
|
genericViewer.onload = (ev) => {};
|
||||||
|
|
||||||
genericViewer.style.display = 'none';
|
genericViewer.style.display = 'none';
|
||||||
genericViewer.src = '';
|
genericViewer.src = '';
|
||||||
|
@ -45,12 +89,21 @@ function onPlay(_event, value: PlayMessage) {
|
||||||
|
|
||||||
imageViewer.src = src;
|
imageViewer.src = src;
|
||||||
imageViewer.style.display = 'block';
|
imageViewer.style.display = 'block';
|
||||||
|
playerControls.style.display = 'block';
|
||||||
}
|
}
|
||||||
else if (src && genericViewer) {
|
else if (src && genericViewer) {
|
||||||
logger.info('Loading generic viewer');
|
logger.info('Loading generic viewer');
|
||||||
|
imageViewer.onload = (ev) => {};
|
||||||
|
|
||||||
|
genericViewer.onload = (ev) => {
|
||||||
|
loadingTimer.stop();
|
||||||
|
loadingSpinner.style.display = 'none';
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.Load);
|
||||||
|
};
|
||||||
|
|
||||||
imageViewer.style.display = 'none';
|
imageViewer.style.display = 'none';
|
||||||
imageViewer.src = '';
|
imageViewer.src = '';
|
||||||
|
playerControls.style.display = 'none';
|
||||||
idleBackground.style.display = 'none';
|
idleBackground.style.display = 'none';
|
||||||
idleIcon.style.display = 'none';
|
idleIcon.style.display = 'none';
|
||||||
|
|
||||||
|
@ -58,9 +111,14 @@ function onPlay(_event, value: PlayMessage) {
|
||||||
genericViewer.style.display = 'block';
|
genericViewer.style.display = 'block';
|
||||||
} else {
|
} else {
|
||||||
logger.error('Error loading content');
|
logger.error('Error loading content');
|
||||||
|
loadingTimer.stop();
|
||||||
|
loadingSpinner.style.display = 'none';
|
||||||
|
imageViewer.onload = (ev) => {};
|
||||||
|
genericViewer.onload = (ev) => {};
|
||||||
|
|
||||||
imageViewer.style.display = 'none';
|
imageViewer.style.display = 'none';
|
||||||
imageViewer.src = '';
|
imageViewer.src = '';
|
||||||
|
playerControls.style.display = 'none';
|
||||||
|
|
||||||
genericViewer.style.display = 'none';
|
genericViewer.style.display = 'none';
|
||||||
genericViewer.src = '';
|
genericViewer.src = '';
|
||||||
|
@ -68,29 +126,6 @@ function onPlay(_event, value: PlayMessage) {
|
||||||
idleBackground.style.display = 'block';
|
idleBackground.style.display = 'block';
|
||||||
idleIcon.style.display = 'block';
|
idleIcon.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMediaItem && cachedPlayMediaItem.showDuration && cachedPlayMediaItem.showDuration > 0) {
|
|
||||||
showDurationTimeout = window.setTimeout(() => {
|
|
||||||
playlistIndex++;
|
|
||||||
|
|
||||||
if (playlistIndex < cachedPlaylist.items.length) {
|
|
||||||
cachedPlayMediaItem = cachedPlaylist.items[playlistIndex];
|
|
||||||
playItemCached = true;
|
|
||||||
window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.info('End of playlist');
|
|
||||||
imageViewer.style.display = 'none';
|
|
||||||
imageViewer.src = '';
|
|
||||||
|
|
||||||
genericViewer.style.display = 'none';
|
|
||||||
genericViewer.src = '';
|
|
||||||
|
|
||||||
idleBackground.style.display = 'block';
|
|
||||||
idleIcon.style.display = 'block';
|
|
||||||
}
|
|
||||||
}, cachedPlayMediaItem.showDuration * 1000);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function onPlayPlaylist(_event, value: PlaylistContent) {
|
function onPlayPlaylist(_event, value: PlaylistContent) {
|
||||||
|
@ -111,28 +146,38 @@ function onPlayPlaylist(_event, value: PlaylistContent) {
|
||||||
window.targetAPI.sendPlayRequest(playMessage, playlistIndex);
|
window.targetAPI.sendPlayRequest(playMessage, playlistIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setPlaylistItem(index: number) {
|
||||||
|
if (index === -1) {
|
||||||
|
logger.info('Looping playlist to end');
|
||||||
|
index = cachedPlaylist.items.length - 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (index === cachedPlaylist.items.length) {
|
||||||
|
logger.info('Looping playlist to start');
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= 0 && index < cachedPlaylist.items.length) {
|
||||||
|
logger.info(`Setting playlist item to index ${index}`);
|
||||||
|
playlistIndex = index;
|
||||||
|
cachedPlayMediaItem = cachedPlaylist.items[playlistIndex];
|
||||||
|
playItemCached = true;
|
||||||
|
window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex);
|
||||||
|
showDurationTimer.stop();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.warn(`Playlist index out of bounds ${index}, ignoring...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
playerCtrlPlaylistLength.textContent= `${playlistIndex+1} of ${cachedPlaylist.items.length}`;
|
||||||
|
}
|
||||||
|
|
||||||
window.targetAPI.onPause(() => { logger.warn('onPause handler invoked for generic content viewer'); });
|
window.targetAPI.onPause(() => { logger.warn('onPause handler invoked for generic content viewer'); });
|
||||||
window.targetAPI.onResume(() => { logger.warn('onResume handler invoked for generic content viewer'); });
|
window.targetAPI.onResume(() => { logger.warn('onResume handler invoked for generic content viewer'); });
|
||||||
window.targetAPI.onSeek((_event, value: SeekMessage) => { logger.warn('onSeek handler invoked for generic content viewer'); });
|
window.targetAPI.onSeek((_event, value: SeekMessage) => { logger.warn('onSeek handler invoked for generic content viewer'); });
|
||||||
window.targetAPI.onSetVolume((_event, value: SetVolumeMessage) => { logger.warn('onSetVolume handler invoked for generic content viewer'); });
|
window.targetAPI.onSetVolume((_event, value: SetVolumeMessage) => { logger.warn('onSetVolume handler invoked for generic content viewer'); });
|
||||||
window.targetAPI.onSetSpeed((_event, value: SetSpeedMessage) => { logger.warn('onSetSpeed handler invoked for generic content viewer'); });
|
window.targetAPI.onSetSpeed((_event, value: SetSpeedMessage) => { logger.warn('onSetSpeed handler invoked for generic content viewer'); });
|
||||||
window.targetAPI.onSetPlaylistItem((_event, value: SetPlaylistItemMessage) => {
|
window.targetAPI.onSetPlaylistItem((_event, value: SetPlaylistItemMessage) => { setPlaylistItem(value.itemIndex); });
|
||||||
if (value.itemIndex >= 0 && value.itemIndex < cachedPlaylist.items.length) {
|
|
||||||
logger.info(`Setting playlist item to index ${value.itemIndex}`);
|
|
||||||
playlistIndex = value.itemIndex;
|
|
||||||
cachedPlayMediaItem = cachedPlaylist.items[playlistIndex];
|
|
||||||
playItemCached = true;
|
|
||||||
window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex);
|
|
||||||
|
|
||||||
if (showDurationTimeout) {
|
|
||||||
window.clearTimeout(showDurationTimeout);
|
|
||||||
showDurationTimeout = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.warn(`Playlist index out of bounds ${value.itemIndex}, ignoring...`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
connectionMonitor.setUiUpdateCallbacks({
|
connectionMonitor.setUiUpdateCallbacks({
|
||||||
onConnect: (connections: string[], initialUpdate: boolean = false) => {
|
onConnect: (connections: string[], initialUpdate: boolean = false) => {
|
||||||
|
@ -152,13 +197,8 @@ enum PlayerControlEvent {
|
||||||
Load,
|
Load,
|
||||||
Pause,
|
Pause,
|
||||||
Play,
|
Play,
|
||||||
VolumeChange,
|
|
||||||
TimeUpdate,
|
|
||||||
UiFadeOut,
|
UiFadeOut,
|
||||||
UiFadeIn,
|
UiFadeIn,
|
||||||
SetCaptions,
|
|
||||||
ToggleSpeedMenu,
|
|
||||||
SetPlaybackRate,
|
|
||||||
ToggleFullscreen,
|
ToggleFullscreen,
|
||||||
ExitFullscreen,
|
ExitFullscreen,
|
||||||
}
|
}
|
||||||
|
@ -172,14 +212,77 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case PlayerControlEvent.Load: {
|
case PlayerControlEvent.Load: {
|
||||||
|
if (isMediaItem) {
|
||||||
|
playerCtrlPlayPrevious.style.display = 'block';
|
||||||
|
playerCtrlPlayNext.style.display = 'block';
|
||||||
|
|
||||||
|
if (cachedPlayMediaItem.showDuration && cachedPlayMediaItem.showDuration > 0) {
|
||||||
|
playerCtrlAction.style.display = 'block';
|
||||||
|
playerCtrlPlaylistLength.style.display = 'none';
|
||||||
|
|
||||||
|
if (imageViewerPlaybackState === PlaybackState.Idle || imageViewerPlaybackState === PlaybackState.Playing) {
|
||||||
|
showDurationTimer.start(cachedPlayMediaItem.showDuration * 1000);
|
||||||
|
playerCtrlAction.setAttribute("class", "pause iconSize");
|
||||||
|
imageViewerPlaybackState = PlaybackState.Playing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
playerCtrlAction.style.display = 'none';
|
||||||
|
playerCtrlPlaylistLength.textContent= `${playlistIndex+1} of ${cachedPlaylist.items.length}`;
|
||||||
|
playerCtrlPlaylistLength.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
playerCtrlPlayPrevious.style.display = 'none';
|
||||||
|
playerCtrlPlayNext.style.display = 'none';
|
||||||
|
playerCtrlAction.style.display = 'none';
|
||||||
|
playerCtrlPlaylistLength.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedPlayMediaItem.metadata && cachedPlayMediaItem.metadata?.type === MetadataType.Generic) {
|
||||||
|
const metadata = cachedPlayMediaItem.metadata as GenericMediaMetadata;
|
||||||
|
|
||||||
|
if (metadata.title) {
|
||||||
|
mediaTitle.innerHTML = metadata.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case PlayerControlEvent.Pause:
|
||||||
|
playerCtrlAction.setAttribute("class", "play iconSize");
|
||||||
|
imageViewerPlaybackState = PlaybackState.Paused;
|
||||||
|
showDurationTimer.pause();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlayerControlEvent.Play:
|
||||||
|
playerCtrlAction.setAttribute("class", "pause iconSize");
|
||||||
|
|
||||||
|
if (imageViewerPlaybackState === PlaybackState.Idle) {
|
||||||
|
setPlaylistItem(0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (showDurationTimer.started) {
|
||||||
|
showDurationTimer.resume();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showDurationTimer.start(cachedPlayMediaItem.showDuration * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
imageViewerPlaybackState = PlaybackState.Playing;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case PlayerControlEvent.UiFadeOut: {
|
case PlayerControlEvent.UiFadeOut: {
|
||||||
|
document.body.style.cursor = "none";
|
||||||
|
playerControls.style.opacity = '0';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case PlayerControlEvent.UiFadeIn: {
|
case PlayerControlEvent.UiFadeIn: {
|
||||||
|
document.body.style.cursor = "default";
|
||||||
|
playerControls.style.opacity = '1';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,61 +291,79 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('keydown', (event: KeyboardEvent) => {
|
// Receiver generated event handlers
|
||||||
|
playerCtrlAction.onclick = () => {
|
||||||
|
if (imageViewerPlaybackState === PlaybackState.Paused || imageViewerPlaybackState === PlaybackState.Idle) {
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.Play);
|
||||||
|
} else {
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.Pause);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
playerCtrlPlayPrevious.onclick = () => { setPlaylistItem(playlistIndex - 1); }
|
||||||
|
playerCtrlPlayNext.onclick = () => { setPlaylistItem(playlistIndex + 1); }
|
||||||
|
|
||||||
|
// Component hiding
|
||||||
|
let uiVisible = true;
|
||||||
|
|
||||||
|
function stopUiHideTimer() {
|
||||||
|
uiHideTimer.stop();
|
||||||
|
|
||||||
|
if (!uiVisible) {
|
||||||
|
uiVisible = true;
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.UiFadeIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.onmouseout = () => {
|
||||||
|
uiHideTimer.stop();
|
||||||
|
uiVisible = false;
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.onmousemove = () => {
|
||||||
|
stopUiHideTimer();
|
||||||
|
uiHideTimer.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
function keyDownEventListener(event: KeyboardEvent) {
|
||||||
// logger.info("KeyDown", event);
|
// logger.info("KeyDown", event);
|
||||||
let handledCase = targetKeyDownEventListener(event);
|
let handledCase = targetKeyDownEventListener(event);
|
||||||
|
|
||||||
if (!handledCase) {
|
if (!handledCase) {
|
||||||
switch (event.code) {
|
switch (event.code) {
|
||||||
case 'ArrowLeft': {
|
case 'ArrowLeft':
|
||||||
// skipBack();
|
setPlaylistItem(playlistIndex - 1);
|
||||||
// event.preventDefault();
|
event.preventDefault();
|
||||||
// handledCase = true;
|
handledCase = true;
|
||||||
|
|
||||||
// const value = { itemIndex: playlistIndex - 1 };
|
|
||||||
// if (value.itemIndex >= 0 && value.itemIndex < cachedPlaylist.items.length) {
|
|
||||||
// logger.info(`Setting playlist item to index ${value.itemIndex}`);
|
|
||||||
// playlistIndex = value.itemIndex;
|
|
||||||
// cachedPlayMediaItem = cachedPlaylist.items[playlistIndex];
|
|
||||||
// playItemCached = true;
|
|
||||||
// window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex);
|
|
||||||
|
|
||||||
// if (showDurationTimeout) {
|
|
||||||
// window.clearTimeout(showDurationTimeout);
|
|
||||||
// showDurationTimeout = null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// logger.warn(`Playlist index out of bounds ${value.itemIndex}, ignoring...`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
case 'ArrowRight':
|
||||||
case 'ArrowRight': {
|
setPlaylistItem(playlistIndex + 1);
|
||||||
// skipForward();
|
event.preventDefault();
|
||||||
// event.preventDefault();
|
handledCase = true;
|
||||||
// handledCase = true;
|
break;
|
||||||
|
case "Home":
|
||||||
// const value = { itemIndex: playlistIndex + 1 };
|
setPlaylistItem(0);
|
||||||
// if (value.itemIndex >= 0 && value.itemIndex < cachedPlaylist.items.length) {
|
event.preventDefault();
|
||||||
// logger.info(`Setting playlist item to index ${value.itemIndex}`);
|
handledCase = true;
|
||||||
// playlistIndex = value.itemIndex;
|
break;
|
||||||
// cachedPlayMediaItem = cachedPlaylist.items[playlistIndex];
|
case "End":
|
||||||
// playItemCached = true;
|
setPlaylistItem(cachedPlaylist.items.length - 1);
|
||||||
// window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex);
|
event.preventDefault();
|
||||||
|
handledCase = true;
|
||||||
// if (showDurationTimeout) {
|
break;
|
||||||
// window.clearTimeout(showDurationTimeout);
|
case 'KeyK':
|
||||||
// showDurationTimeout = null;
|
case 'Space':
|
||||||
// }
|
case 'Enter':
|
||||||
// }
|
// Play/pause toggle
|
||||||
// else {
|
if (imageViewerPlaybackState === PlaybackState.Paused || imageViewerPlaybackState === PlaybackState.Idle) {
|
||||||
// logger.warn(`Playlist index out of bounds ${value.itemIndex}, ignoring...`);
|
playerCtrlStateUpdate(PlayerControlEvent.Play);
|
||||||
// }
|
} else {
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.Pause);
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
handledCase = true;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -251,7 +372,9 @@ document.addEventListener('keydown', (event: KeyboardEvent) => {
|
||||||
if (window.targetAPI.getSubscribedKeys().keyDown.has(event.key)) {
|
if (window.targetAPI.getSubscribedKeys().keyDown.has(event.key)) {
|
||||||
window.targetAPI.sendEvent(new EventMessage(Date.now(), new KeyEvent(EventType.KeyDown, event.key, event.repeat, handledCase)));
|
window.targetAPI.sendEvent(new EventMessage(Date.now(), new KeyEvent(EventType.KeyDown, event.key, event.repeat, handledCase)));
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', keyDownEventListener);
|
||||||
document.addEventListener('keyup', (event: KeyboardEvent) => {
|
document.addEventListener('keyup', (event: KeyboardEvent) => {
|
||||||
if (window.targetAPI.getSubscribedKeys().keyUp.has(event.key)) {
|
if (window.targetAPI.getSubscribedKeys().keyUp.has(event.key)) {
|
||||||
window.targetAPI.sendEvent(new EventMessage(Date.now(), new KeyEvent(EventType.KeyUp, event.key, event.repeat, false)));
|
window.targetAPI.sendEvent(new EventMessage(Date.now(), new KeyEvent(EventType.KeyUp, event.key, event.repeat, false)));
|
||||||
|
@ -260,6 +383,10 @@ document.addEventListener('keyup', (event: KeyboardEvent) => {
|
||||||
|
|
||||||
export {
|
export {
|
||||||
PlayerControlEvent,
|
PlayerControlEvent,
|
||||||
|
idleBackground,
|
||||||
|
idleIcon,
|
||||||
|
imageViewer,
|
||||||
|
genericViewer,
|
||||||
onPlay,
|
onPlay,
|
||||||
playerCtrlStateUpdate,
|
playerCtrlStateUpdate,
|
||||||
};
|
};
|
||||||
|
|
|
@ -32,7 +32,7 @@ body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#title-icon {
|
#titleIcon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
|
@ -42,6 +42,122 @@ body {
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
|
||||||
|
/* height: 100%; */
|
||||||
|
height: 120px;
|
||||||
|
width: 100%;
|
||||||
|
/* background: linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 100%); */
|
||||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.0) 35%);
|
||||||
|
|
||||||
|
background-size: 100% 300px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: bottom;
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconSize {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 24px;
|
||||||
|
height: 24px;
|
||||||
|
/* width: calc(50% - 24px); */
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leftButtonContainer {
|
||||||
|
left: 24px;
|
||||||
|
right: 60%;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
font-family: InterVariable;
|
||||||
|
font-size: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
#centerButtonContainer {
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0%);
|
||||||
|
|
||||||
|
font-family: InterVariable;
|
||||||
|
font-size: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rightButtonContainer {
|
||||||
|
right: 24px;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mediaTitle {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play {
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
background-image: url("../assets/icons/player/icon24_play.svg");
|
||||||
|
transition: background-image 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play:hover {
|
||||||
|
background-image: url("../assets/icons/player/icon24_play_active.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.pause {
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
background-image: url("../assets/icons/player/icon24_pause.svg");
|
||||||
|
transition: background-image 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pause:hover {
|
||||||
|
background-image: url("../assets/icons/player/icon24_pause_active.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.playPrevious {
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
background-image: url("../assets/icons/player/icon24_play_previous.svg");
|
||||||
|
transition: background-image 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playPrevious:hover {
|
||||||
|
background-image: url("../assets/icons/player/icon24_play_previous_active.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.playNext {
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
background-image: url("../assets/icons/player/icon24_play_next.svg");
|
||||||
|
transition: background-image 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playNext:hover {
|
||||||
|
background-image: url("../assets/icons/player/icon24_play_next_active.svg");
|
||||||
|
}
|
||||||
|
|
||||||
.lds-ring {
|
.lds-ring {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -148,7 +264,7 @@ body {
|
||||||
|
|
||||||
/* Display scaling (Minimum supported resolution is 960x540) */
|
/* Display scaling (Minimum supported resolution is 960x540) */
|
||||||
@media only screen and ((min-width: 2560px) or (min-height: 1440px)) {
|
@media only screen and ((min-width: 2560px) or (min-height: 1440px)) {
|
||||||
#title-icon {
|
#titleIcon {
|
||||||
width: 164px;
|
width: 164px;
|
||||||
height: 164px;
|
height: 164px;
|
||||||
}
|
}
|
||||||
|
@ -179,7 +295,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and ((max-width: 2559px) or (max-height: 1439px)) {
|
@media only screen and ((max-width: 2559px) or (max-height: 1439px)) {
|
||||||
#title-icon {
|
#titleIcon {
|
||||||
width: 124px;
|
width: 124px;
|
||||||
height: 124px;
|
height: 124px;
|
||||||
}
|
}
|
||||||
|
@ -210,7 +326,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and ((max-width: 1919px) or (max-height: 1079px)) {
|
@media only screen and ((max-width: 1919px) or (max-height: 1079px)) {
|
||||||
#title-icon {
|
#titleIcon {
|
||||||
width: 84px;
|
width: 84px;
|
||||||
height: 84px;
|
height: 84px;
|
||||||
}
|
}
|
||||||
|
@ -241,7 +357,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and ((max-width: 1279px) or (max-height: 719px)) {
|
@media only screen and ((max-width: 1279px) or (max-height: 719px)) {
|
||||||
#title-icon {
|
#titleIcon {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
import { PlayerControlEvent, playerCtrlStateUpdate } from 'common/viewer/Renderer';
|
import { PlayerControlEvent, playerCtrlStateUpdate, idleBackground, idleIcon, imageViewer, genericViewer } from 'common/viewer/Renderer';
|
||||||
|
|
||||||
|
const playerCtrlFullscreen = document.getElementById("fullscreen");
|
||||||
|
playerCtrlFullscreen.onclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
|
||||||
|
idleBackground.ondblclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
|
||||||
|
idleIcon.ondblclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
|
||||||
|
imageViewer.ondblclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
|
||||||
|
genericViewer.ondblclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
|
||||||
|
|
||||||
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
|
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
|
||||||
let handledCase = false;
|
let handledCase = false;
|
||||||
|
@ -7,13 +14,13 @@ export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean
|
||||||
case PlayerControlEvent.ToggleFullscreen: {
|
case PlayerControlEvent.ToggleFullscreen: {
|
||||||
window.electronAPI.toggleFullScreen();
|
window.electronAPI.toggleFullScreen();
|
||||||
|
|
||||||
// window.electronAPI.isFullScreen().then((isFullScreen: boolean) => {
|
window.electronAPI.isFullScreen().then((isFullScreen: boolean) => {
|
||||||
// if (isFullScreen) {
|
if (isFullScreen) {
|
||||||
// playerCtrlFullscreen.setAttribute("class", "fullscreen_on");
|
playerCtrlFullscreen.setAttribute("class", "fullscreen_on");
|
||||||
// } else {
|
} else {
|
||||||
// playerCtrlFullscreen.setAttribute("class", "fullscreen_off");
|
playerCtrlFullscreen.setAttribute("class", "fullscreen_off");
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
|
||||||
handledCase = true;
|
handledCase = true;
|
||||||
break;
|
break;
|
||||||
|
@ -21,7 +28,7 @@ export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean
|
||||||
|
|
||||||
case PlayerControlEvent.ExitFullscreen:
|
case PlayerControlEvent.ExitFullscreen:
|
||||||
window.electronAPI.exitFullScreen();
|
window.electronAPI.exitFullScreen();
|
||||||
// playerCtrlFullscreen.setAttribute("class", "fullscreen_off");
|
playerCtrlFullscreen.setAttribute("class", "fullscreen_off");
|
||||||
|
|
||||||
handledCase = true;
|
handledCase = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -6,16 +6,33 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="stylesheet" href="../assets/fonts/inter.css" />
|
<link rel="stylesheet" href="../assets/fonts/inter.css" />
|
||||||
<link rel="stylesheet" href="./common.css" />
|
<link rel="stylesheet" href="./common.css" />
|
||||||
|
<link rel="stylesheet" href="./style.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Empty video element as a workaround to fix issue with white border outline without it... -->
|
<!-- Empty video element as a workaround to fix issue with white border outline without it... -->
|
||||||
<video id="video-player" class="video"></video>
|
<video id="idleBackground" class="video"></video>
|
||||||
<div id="viewer" class="viewer">
|
<div id="viewer" class="viewer">
|
||||||
<div id="title-icon"></div>
|
<div id="titleIcon"></div>
|
||||||
<img id="viewer-image" class="viewer" />
|
<div id="loadingSpinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||||
<iframe id="viewer-generic" class="viewer"></iframe>
|
<img id="viewerImage" class="viewer" />
|
||||||
|
<iframe id="viewerGeneric" class="viewer"></iframe>
|
||||||
</div>>
|
</div>>
|
||||||
|
|
||||||
|
<div id="controls" class="container">
|
||||||
|
<div id="leftButtonContainer" class="buttonContainer">
|
||||||
|
<div id="mediaTitle"></div>
|
||||||
|
</div>
|
||||||
|
<div id="centerButtonContainer" class="buttonContainer">
|
||||||
|
<div id="playPrevious" class="playPrevious iconSize" style="display: none"></div>
|
||||||
|
<div id="action" class="play iconSize" style="display: none"></div>
|
||||||
|
<div id="playlistLength" style="display: none"></div>
|
||||||
|
<div id="playNext" class="playNext iconSize" style="display: none"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="rightButtonContainer" class="buttonContainer">
|
||||||
|
<div id="fullscreen" class="fullscreen_on iconSize"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="toast-notification">
|
<div id="toast-notification">
|
||||||
<div id="toast-icon"></div>
|
<div id="toast-icon"></div>
|
||||||
|
@ -24,4 +41,4 @@
|
||||||
|
|
||||||
<script src="./renderer.js"></script>
|
<script src="./renderer.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
26
receivers/electron/src/viewer/style.css
Normal file
26
receivers/electron/src/viewer/style.css
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
.fullscreen_on {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
background-image: url("../assets/icons/player/icon24_fullscreen_on.svg");
|
||||||
|
transition: background-image 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen_on:hover {
|
||||||
|
background-image: url("../assets/icons/player/icon24_fullscreen_on_active.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen_off {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
background-image: url("../assets/icons/player/icon24_fullscreen_off.svg");
|
||||||
|
transition: background-image 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen_off:hover {
|
||||||
|
background-image: url("../assets/icons/player/icon24_fullscreen_off_active.svg");
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue