1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-09-02 20:43:06 +00:00

webOS: Add support for remote control bar

This commit is contained in:
Michael Hollister 2025-07-21 14:30:47 -05:00
parent 144c3e17f5
commit 8dde1ec5b3
20 changed files with 855 additions and 232 deletions

View file

@ -22,6 +22,8 @@ export class Timer {
private delay: number;
private startTime: number;
private remainingTime: number;
private enabled: boolean;
public started: boolean;
constructor(callback: () => void, delay: number, autoStart: boolean = true) {
@ -29,6 +31,7 @@ export class Timer {
this.callback = callback;
this.delay = delay;
this.started = false;
this.enabled = true;
if (autoStart) {
this.start();
@ -36,20 +39,22 @@ export class Timer {
}
public start(delay?: number) {
this.delay = delay ? delay : this.delay;
if (this.enabled) {
this.delay = delay ? delay : this.delay;
if (this.handle) {
window.clearTimeout(this.handle);
if (this.handle) {
window.clearTimeout(this.handle);
}
this.started = true;
this.startTime = Date.now();
this.remainingTime = null;
this.handle = window.setTimeout(this.callback, this.delay);
}
this.started = true;
this.startTime = Date.now();
this.remainingTime = null;
this.handle = window.setTimeout(this.callback, this.delay);
}
public pause() {
if (this.handle) {
if (this.enabled && this.handle) {
window.clearTimeout(this.handle);
this.handle = null;
this.remainingTime = this.delay - (Date.now() - this.startTime);
@ -57,7 +62,7 @@ export class Timer {
}
public resume() {
if (this.remainingTime) {
if (this.enabled && this.remainingTime) {
this.start(this.remainingTime);
}
}
@ -70,4 +75,32 @@ export class Timer {
this.started = false;
}
}
public end() {
this.stop();
this.callback();
}
public enable() {
this.enabled = true;
}
public disable() {
this.enabled = false;
this.stop();
}
public setDelay(delay: number) {
this.stop();
this.delay = delay;
}
public setCallback(callback: () => void) {
this.stop();
this.callback = callback;
}
public isPaused(): boolean {
return this.remainingTime !== null;
}
}

View file

@ -86,13 +86,14 @@ if (TARGET === 'electron') {
} else if (TARGET === 'webOS' || TARGET === 'tizenOS') {
preloadData.onDeviceInfoCb = () => { logger.warn('Main: Callback not set while fetching device info'); };
preloadData.getSessionsCb = () => { logger.error('Main: Callback not set while calling getSessions'); };
preloadData.initializeSubscribedKeysCb = () => { logger.error('Main: Callback not set while calling initializeSubscribedKeys'); };
preloadData.onConnectCb = (_, value: any) => { logger.error('Main: Callback not set while calling onConnect'); };
preloadData.onDisconnectCb = (_, value: any) => { logger.error('Main: Callback not set while calling onDisconnect'); };
preloadData.sendEventCb = (message: EventMessage) => { logger.error('Main: Callback not set while calling onSendEventCb'); };
preloadData.onEventSubscribedKeysUpdate = (value: { keyDown: Set<string>, keyUp: Set<string> }) => {
preloadData.subscribedKeys.keyDown = value.keyDown;
preloadData.subscribedKeys.keyUp = value.keyUp;
preloadData.onEventSubscribedKeysUpdate = (value: { keyDown: string[], keyUp: string[] }) => {
preloadData.subscribedKeys.keyDown = new Set(value.keyDown);
preloadData.subscribedKeys.keyUp = new Set(value.keyUp);
};
preloadData.onToast = (message: string, icon: ToastIcon = ToastIcon.INFO, duration: number = 5000) => {
@ -110,6 +111,17 @@ if (TARGET === 'electron') {
return preloadData.getSessionsCb();
}
},
initializeSubscribedKeys: (callback?: () => Promise<{ keyDown: string[], keyUp: string[] }>) => {
if (callback) {
preloadData.initializeSubscribedKeysCb = callback;
}
else {
preloadData.initializeSubscribedKeysCb().then((value: { keyDown: Set<string>, keyUp: Set<string> }) => {
preloadData.subscribedKeys.keyDown = new Set(value.keyDown);
preloadData.subscribedKeys.keyUp = new Set(value.keyUp);
});
}
},
getSubscribedKeys: () => preloadData.subscribedKeys,
onConnect: (callback: (_, value: any) => void) => preloadData.onConnectCb = callback,
onDisconnect: (callback: (_, value: any) => void) => preloadData.onDisconnectCb = callback,

View file

@ -212,7 +212,7 @@ export function keyDownEventHandler(event: KeyboardEvent) {
let key = (TARGET === 'webOS' && result.key !== '') ? result.key : event.key;
if (!handledCase) {
switch (event.key) {
switch (event.key.toLowerCase()) {
default:
break;
}
@ -232,7 +232,7 @@ export function keyUpEventHandler(event: KeyboardEvent) {
let key = (TARGET === 'webOS' && result.key !== '') ? result.key : event.key;
if (!handledCase) {
switch (event.key) {
switch (event.key.toLowerCase()) {
default:
break;
}

View file

@ -14,6 +14,8 @@ export class Player {
private player: HTMLVideoElement;
private playMessage: PlayMessage;
private source: string;
private playCb: any;
private pauseCb: any;
// Todo: use a common event handler interface instead of exposing internal players
public playerType: PlayerType;
@ -23,6 +25,8 @@ export class Player {
constructor(player: HTMLVideoElement, message: PlayMessage) {
this.player = player;
this.playMessage = message;
this.playCb = null;
this.pauseCb = null;
if (message.container === 'application/dash+xml') {
this.playerType = PlayerType.Dash;
@ -110,6 +114,8 @@ export class Player {
this.hlsPlayer = null;
this.playMessage = null;
this.source = null;
this.playCb = null;
this.pauseCb = null;
}
/**
@ -143,6 +149,10 @@ export class Player {
} else { // HLS, HTML
this.player.play();
}
if (this.playCb) {
this.playCb();
}
}
public isPaused(): boolean {
@ -161,6 +171,15 @@ export class Player {
} else { // HLS, HTML
this.player.pause();
}
if (this.pauseCb) {
this.pauseCb();
}
}
public setPlayPauseCallback(playCallback: (() => void), pauseCallback: (() => void)) {
this.playCb = playCallback;
this.pauseCb = pauseCallback;
}
public stop() {

View file

@ -19,7 +19,6 @@ declare global {
interface Window {
electronAPI: any;
tizenOSAPI: any;
webOSAPI: any;
webOS: any;
targetAPI: any;
}
@ -90,13 +89,14 @@ if (TARGET === 'electron') {
preloadData.sendPlayRequestCb = () => { logger.error('Player: Callback "sendPlayRequest" not set'); };
preloadData.getSessionsCb = () => { logger.error('Player: Callback "getSessions" not set'); };
preloadData.onConnectCb = () => { logger.error('Player: Callback "onConnect" not set'); };
preloadData.onDisconnectCb = () => { logger.error('Player: Callback "onDisconnect" not set'); };
preloadData.initializeSubscribedKeysCb = () => { logger.error('Player: Callback "initializeSubscribedKeys" not set'); };
preloadData.onConnectCb = () => { logger.warn('Player: Callback "onConnect" not set'); };
preloadData.onDisconnectCb = () => { logger.warn('Player: Callback "onDisconnect" not set'); };
preloadData.onPlayPlaylistCb = () => { logger.error('Player: Callback "onPlayPlaylist" not set'); };
preloadData.onEventSubscribedKeysUpdate = (value: { keyDown: Set<string>, keyUp: Set<string> }) => {
preloadData.subscribedKeys.keyDown = value.keyDown;
preloadData.subscribedKeys.keyUp = value.keyUp;
preloadData.onEventSubscribedKeysUpdate = (value: { keyDown: string[], keyUp: string[] }) => {
preloadData.subscribedKeys.keyDown = new Set(value.keyDown);
preloadData.subscribedKeys.keyUp = new Set(value.keyUp);
};
preloadData.onToast = (message: string, icon: ToastIcon = ToastIcon.INFO, duration: number = 5000) => {
@ -125,6 +125,17 @@ if (TARGET === 'electron') {
return preloadData.getSessionsCb();
}
},
initializeSubscribedKeys: (callback?: () => Promise<{ keyDown: string[], keyUp: string[] }>) => {
if (callback) {
preloadData.initializeSubscribedKeysCb = callback;
}
else {
preloadData.initializeSubscribedKeysCb().then((value: { keyDown: Set<string>, keyUp: Set<string> }) => {
preloadData.subscribedKeys.keyDown = new Set(value.keyDown);
preloadData.subscribedKeys.keyUp = new Set(value.keyUp);
});
}
},
getSubscribedKeys: () => preloadData.subscribedKeys,
onConnect: (callback: any) => { preloadData.onConnectCb = callback; },
onDisconnect: (callback: any) => { preloadData.onDisconnectCb = callback; },

View file

@ -40,7 +40,7 @@ const playerCtrlProgressBarBuffer = document.getElementById("progressBarBuffer")
const playerCtrlProgressBarProgress = document.getElementById("progressBarProgress");
const playerCtrlProgressBarPosition = document.getElementById("progressBarPosition");
const playerCtrlProgressBarHandle = document.getElementById("progressBarHandle");
const PlayerCtrlProgressBarInteractiveArea = document.getElementById("progressBarInteractiveArea");
const playerCtrlProgressBarInteractiveArea = document.getElementById("progressBarInteractiveArea");
const playerCtrlVolumeBar = document.getElementById("volumeBar");
const playerCtrlVolumeBarProgress = document.getElementById("volumeBarProgress");
@ -80,10 +80,7 @@ let playlistIndex = 0;
let isMediaItem = false;
let playItemCached = false;
let uiHideTimer = new Timer(() => {
uiVisible = false;
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
}, 3000);
let uiHideTimer = new Timer(() => { playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut); }, 3000);
let loadingTimer = new Timer(() => { loadingSpinner.style.display = 'block'; }, 100, false);
let showDurationTimer = new Timer(mediaEndHandler, 0, false);
let mediaTitleShowTimer = new Timer(() => { mediaTitle.style.display = 'none'; }, 5000);
@ -567,6 +564,7 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
}
case PlayerControlEvent.UiFadeOut: {
uiVisible = false;
document.body.style.cursor = "none";
playerControls.style.opacity = '0';
captionsBaseHeight = captionsBaseHeightCollapsed;
@ -582,6 +580,7 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
}
case PlayerControlEvent.UiFadeIn: {
uiVisible = true;
document.body.style.cursor = "default";
playerControls.style.opacity = '1';
captionsBaseHeight = captionsBaseHeightExpanded;
@ -644,7 +643,7 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
function scrubbingMouseUIHandler(e: MouseEvent) {
const progressBarOffset = e.offsetX - playerCtrlProgressBar.offsetLeft;
const progressBarWidth = PlayerCtrlProgressBarInteractiveArea.offsetWidth - (playerCtrlProgressBar.offsetLeft * 2);
const progressBarWidth = playerCtrlProgressBarInteractiveArea.offsetWidth - (playerCtrlProgressBar.offsetLeft * 2);
let time = isLive ? Math.round((1 - (progressBarOffset / progressBarWidth)) * player?.getDuration()) : Math.round((progressBarOffset / progressBarWidth) * player?.getDuration());
time = Math.min(player?.getDuration(), Math.max(0.0, time));
@ -657,7 +656,7 @@ function scrubbingMouseUIHandler(e: MouseEvent) {
playerCtrlProgressBarPosition.textContent = isLive ? `${livePrefix}${formatDuration(time)}` : formatDuration(time);
let offset = e.offsetX - (playerCtrlProgressBarPosition.offsetWidth / 2);
offset = Math.min(PlayerCtrlProgressBarInteractiveArea.offsetWidth - (playerCtrlProgressBarPosition.offsetWidth / 1), Math.max(8, offset));
offset = Math.min(playerCtrlProgressBarInteractiveArea.offsetWidth - (playerCtrlProgressBarPosition.offsetWidth / 1), Math.max(8, offset));
playerCtrlProgressBarPosition.setAttribute("style", `display: block; left: ${offset}px`);
}
@ -674,21 +673,21 @@ playerCtrlPlayPrevious.onclick = () => { setPlaylistItem(playlistIndex - 1); }
playerCtrlPlayNext.onclick = () => { setPlaylistItem(playlistIndex + 1); }
playerCtrlVolume.onclick = () => { player?.setMute(!player?.isMuted()); };
PlayerCtrlProgressBarInteractiveArea.onmousedown = (e: MouseEvent) => { scrubbing = true; scrubbingMouseHandler(e) };
PlayerCtrlProgressBarInteractiveArea.onmouseup = () => { scrubbing = false; };
PlayerCtrlProgressBarInteractiveArea.onmouseenter = (e: MouseEvent) => {
playerCtrlProgressBarInteractiveArea.onmousedown = (e: MouseEvent) => { scrubbing = true; scrubbingMouseHandler(e) };
playerCtrlProgressBarInteractiveArea.onmouseup = () => { scrubbing = false; };
playerCtrlProgressBarInteractiveArea.onmouseenter = (e: MouseEvent) => {
if (e.buttons === 0) {
volumeChanging = false;
}
scrubbingMouseUIHandler(e);
};
PlayerCtrlProgressBarInteractiveArea.onmouseleave = () => { playerCtrlProgressBarPosition.setAttribute("style", "display: none"); };
PlayerCtrlProgressBarInteractiveArea.onmousemove = (e: MouseEvent) => { scrubbingMouseHandler(e) };
playerCtrlProgressBarInteractiveArea.onmouseleave = () => { playerCtrlProgressBarPosition.setAttribute("style", "display: none"); };
playerCtrlProgressBarInteractiveArea.onmousemove = (e: MouseEvent) => { scrubbingMouseHandler(e) };
function scrubbingMouseHandler(e: MouseEvent) {
const progressBarOffset = e.offsetX - playerCtrlProgressBar.offsetLeft;
const progressBarWidth = PlayerCtrlProgressBarInteractiveArea.offsetWidth - (playerCtrlProgressBar.offsetLeft * 2);
const progressBarWidth = playerCtrlProgressBarInteractiveArea.offsetWidth - (playerCtrlProgressBar.offsetLeft * 2);
let time = Math.round((progressBarOffset / progressBarWidth) * player?.getDuration());
time = Math.min(player?.getDuration(), Math.max(0.0, time));
@ -880,17 +879,11 @@ function stopUiHideTimer() {
uiHideTimer.stop();
if (!uiVisible) {
uiVisible = true;
playerCtrlStateUpdate(PlayerControlEvent.UiFadeIn);
}
}
document.onmouseout = () => {
uiHideTimer.stop();
uiVisible = false;
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
}
document.onmouseout = () => { uiHideTimer.end(); }
document.onmousemove = () => {
stopUiHideTimer();
@ -910,16 +903,62 @@ document.addEventListener('click', (event: MouseEvent) => {
});
// Add the keydown event listener to the document
const skipInterval = 10;
const minSkipInterval = 10;
const volumeIncrement = 0.1;
function skipBack() {
player?.setCurrentTime(Math.max(player?.getCurrentTime() - skipInterval, 0));
let skipBackRepeat = false;
let skipBackInterval = minSkipInterval;
let skipBackIntervalIncrease = false;
let skipBackTimer = new Timer(() => { skipBackIntervalIncrease = true; }, 2000, false);
let skipForwardRepeat = false;
let skipForwardInterval = minSkipInterval;
let skipForwardIntervalIncrease = false;
let skipForwardTimer = new Timer(() => { skipForwardIntervalIncrease = true; }, 2000, false);
function skipBack(repeat: boolean = false) {
if (!skipBackRepeat && repeat) {
skipBackRepeat = true;
skipBackTimer.start();
}
else if (skipBackRepeat && skipBackIntervalIncrease && repeat) {
skipBackInterval = skipBackInterval === 10 ? 30 : Math.min(skipBackInterval + 30, 300);
skipBackIntervalIncrease = false;
skipBackTimer.start();
}
else if (!repeat) {
skipBackTimer.stop();
skipBackRepeat = false;
skipBackIntervalIncrease = false;
skipBackInterval = minSkipInterval;
}
player?.setCurrentTime(Math.max(player?.getCurrentTime() - skipBackInterval, 0));
// Force time update since player triggered update only occurs in real-time if skipping within loaded buffer
playerCtrlStateUpdate(PlayerControlEvent.TimeUpdate);
}
function skipForward() {
function skipForward(repeat: boolean = false) {
if (!skipForwardRepeat && repeat) {
skipForwardRepeat = true;
skipForwardTimer.start();
}
else if (skipForwardRepeat && skipForwardIntervalIncrease && repeat) {
skipForwardInterval = skipForwardInterval === 10 ? 30 : Math.min(skipForwardInterval + 30, 300);
skipForwardIntervalIncrease = false;
skipForwardTimer.start();
}
else if (!repeat) {
skipForwardTimer.stop();
skipForwardRepeat = false;
skipForwardIntervalIncrease = false;
skipForwardInterval = minSkipInterval;
}
if (!isLivePosition) {
player?.setCurrentTime(Math.min(player?.getCurrentTime() + skipInterval, player?.getDuration()));
player?.setCurrentTime(Math.min(player?.getCurrentTime() + skipForwardInterval, player?.getDuration()));
// Force time update since player triggered update only occurs in real-time if skipping within loaded buffer
playerCtrlStateUpdate(PlayerControlEvent.TimeUpdate);
}
}
@ -934,12 +973,12 @@ function keyDownEventHandler(event: KeyboardEvent) {
if (!handledCase) {
switch (event.key.toLowerCase()) {
case 'arrowleft':
skipBack();
skipBack(event.repeat);
event.preventDefault();
handledCase = true;
break;
case 'arrowright':
skipForward();
skipForward(event.repeat);
event.preventDefault();
handledCase = true;
break;
@ -1004,7 +1043,7 @@ function keyUpEventHandler(event: KeyboardEvent) {
let key = (TARGET === 'webOS' && result.key !== '') ? result.key : event.key;
if (!handledCase) {
switch (event.key) {
switch (event.key.toLowerCase()) {
default:
break;
}
@ -1025,25 +1064,18 @@ export {
idleIcon,
videoElement,
videoCaptions,
playerCtrlProgressBar,
playerCtrlProgressBarBuffer,
playerCtrlProgressBarProgress,
playerCtrlProgressBarHandle,
playerCtrlVolumeBar,
playerCtrlVolumeBarProgress,
playerCtrlVolumeBarHandle,
playerCtrlLiveBadge,
playerCtrlPosition,
playerCtrlDuration,
playerCtrlCaptions,
player,
uiHideTimer,
isLive,
playlistIndex,
captionsBaseHeight,
captionsLineHeight,
onPlay,
onPlayPlaylist,
setPlaylistItem,
playerCtrlStateUpdate,
formatDuration,
skipBack,
skipForward,
keyDownEventHandler,

View file

@ -5,6 +5,7 @@ import * as connectionMonitor from 'common/ConnectionMonitor';
import { toast, ToastIcon } from 'common/components/Toast';
import {
targetPlayerCtrlStateUpdate,
targetPlayerCtrlPostStateUpdate,
targetKeyDownEventListener,
targetKeyUpEventListener,
} from 'src/viewer/Renderer';
@ -34,10 +35,7 @@ let isMediaItem = false;
let playItemCached = false;
let imageViewerPlaybackState: PlaybackState = PlaybackState.Idle;
let uiHideTimer = new Timer(() => {
uiVisible = false;
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
}, 3000);
let uiHideTimer = new Timer(() => { playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut); }, 3000);
let loadingTimer = new Timer(() => { loadingSpinner.style.display = 'block'; }, 100, false);
let showDurationTimer = new Timer(() => {
@ -278,12 +276,14 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
break;
case PlayerControlEvent.UiFadeOut: {
uiVisible = false;
document.body.style.cursor = "none";
playerControls.style.opacity = '0';
break;
}
case PlayerControlEvent.UiFadeIn: {
uiVisible = true;
document.body.style.cursor = "default";
playerControls.style.opacity = '1';
break;
@ -292,6 +292,8 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
default:
break;
}
targetPlayerCtrlPostStateUpdate(event);
}
// Receiver generated event handlers
@ -313,17 +315,11 @@ function stopUiHideTimer() {
uiHideTimer.stop();
if (!uiVisible) {
uiVisible = true;
playerCtrlStateUpdate(PlayerControlEvent.UiFadeIn);
}
}
document.onmouseout = () => {
uiHideTimer.stop();
uiVisible = false;
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
}
document.onmouseout = () => { uiHideTimer.end(); }
document.onmousemove = () => {
stopUiHideTimer();
uiHideTimer.start();
@ -337,37 +333,40 @@ function keyDownEventHandler(event: KeyboardEvent) {
// @ts-ignore
let key = (TARGET === 'webOS' && result.key !== '') ? result.key : event.key;
if (!handledCase) {
switch (event.code) {
case 'ArrowLeft':
if (!handledCase && isMediaItem) {
switch (event.key.toLowerCase()) {
case 'arrowleft':
setPlaylistItem(playlistIndex - 1);
event.preventDefault();
handledCase = true;
break;
case 'ArrowRight':
case 'arrowright':
setPlaylistItem(playlistIndex + 1);
event.preventDefault();
handledCase = true;
break;
case "Home":
case "home":
setPlaylistItem(0);
event.preventDefault();
handledCase = true;
break;
case "End":
case "end":
setPlaylistItem(cachedPlaylist.items.length - 1);
event.preventDefault();
handledCase = true;
break;
case 'KeyK':
case 'Space':
case 'Enter':
case 'k':
case ' ':
case 'enter':
// Play/pause toggle
if (imageViewerPlaybackState === PlaybackState.Paused || imageViewerPlaybackState === PlaybackState.Idle) {
playerCtrlStateUpdate(PlayerControlEvent.Play);
} else {
playerCtrlStateUpdate(PlayerControlEvent.Pause);
if (cachedPlayMediaItem.showDuration && cachedPlayMediaItem.showDuration > 0) {
if (imageViewerPlaybackState === PlaybackState.Paused || imageViewerPlaybackState === PlaybackState.Idle) {
playerCtrlStateUpdate(PlayerControlEvent.Play);
} else {
playerCtrlStateUpdate(PlayerControlEvent.Pause);
}
}
event.preventDefault();
handledCase = true;
break;
@ -390,7 +389,7 @@ function keyUpEventHandler(event: KeyboardEvent) {
let key = (TARGET === 'webOS' && result.key !== '') ? result.key : event.key;
if (!handledCase) {
switch (event.key) {
switch (event.key.toLowerCase()) {
default:
break;
}
@ -410,7 +409,12 @@ export {
idleIcon,
imageViewer,
genericViewer,
uiHideTimer,
showDurationTimer,
isMediaItem,
playlistIndex,
cachedPlayMediaItem,
imageViewerPlaybackState,
onPlay,
onPlayPlaylist,
playerCtrlStateUpdate,

View file

@ -44,6 +44,14 @@ export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean
return handledCase;
}
export function targetPlayerCtrlPostStateUpdate(event: PlayerControlEvent) {
// Currently unused in electron player
switch (event) {
default:
break;
}
}
export function targetKeyDownEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
let handledCase = false;

View file

@ -40,6 +40,14 @@ export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean
return handledCase;
}
export function targetPlayerCtrlPostStateUpdate(event: PlayerControlEvent) {
// Currently unused in electron player
switch (event) {
default:
break;
}
}
export function targetKeyDownEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
let handledCase = false;

View file

@ -175,6 +175,22 @@ export class Main {
return;
}
case 'get_subscribed_keys': {
const tcpListenerSubscribedKeys = Main.tcpListenerService.getAllSubscribedKeys();
const webSocketListenerSubscribedKeys = Main.webSocketListenerService.getAllSubscribedKeys();
// webOS compatibility: Need to convert set objects to array objects since data needs to be JSON compatible
const subscribeData = {
keyDown: Array.from(new Set([...tcpListenerSubscribedKeys.keyDown, ...webSocketListenerSubscribedKeys.keyDown])),
keyUp: Array.from(new Set([...tcpListenerSubscribedKeys.keyUp, ...webSocketListenerSubscribedKeys.keyUp])),
};
message.respond({
returnValue: true,
value: subscribeData
});
return;
}
case 'network_changed': {
logger.info('Network interfaces have changed', message);
Main.discoveryService.stop();
@ -240,17 +256,34 @@ export class Main {
});
l.emitter.on('setplaylistitem', (message: SetPlaylistItemMessage) => Main.emitter.emit('setplaylistitem', message));
l.emitter.on('subscribeevent', (message) => {
const subscribeData = l.subscribeEvent(message.sessionId, message.body.event);
l.subscribeEvent(message.sessionId, message.body.event);
if (message.body.event.type === EventType.KeyDown.valueOf() || message.body.event.type === EventType.KeyUp.valueOf()) {
const tcpListenerSubscribedKeys = Main.tcpListenerService.getAllSubscribedKeys();
const webSocketListenerSubscribedKeys = Main.webSocketListenerService.getAllSubscribedKeys();
// webOS compatibility: Need to convert set objects to array objects since data needs to be JSON compatible
const subscribeData = {
keyDown: Array.from(new Set([...tcpListenerSubscribedKeys.keyDown, ...webSocketListenerSubscribedKeys.keyDown])),
keyUp: Array.from(new Set([...tcpListenerSubscribedKeys.keyUp, ...webSocketListenerSubscribedKeys.keyUp])),
};
console.log('emitting set info ON SUBSCRIBE ONLY', subscribeData)
Main.emitter.emit('event_subscribed_keys_update', subscribeData);
}
});
l.emitter.on('unsubscribeevent', (message) => {
const unsubscribeData = l.unsubscribeEvent(message.sessionId, message.body.event);
l.unsubscribeEvent(message.sessionId, message.body.event);
if (message.body.event.type === EventType.KeyDown.valueOf() || message.body.event.type === EventType.KeyUp.valueOf()) {
Main.emitter.emit('event_subscribed_keys_update', unsubscribeData);
const tcpListenerSubscribedKeys = Main.tcpListenerService.getAllSubscribedKeys();
const webSocketListenerSubscribedKeys = Main.webSocketListenerService.getAllSubscribedKeys();
// webOS compatibility: Need to convert set objects to array objects since data needs to be JSON compatible
const subscribeData = {
keyDown: Array.from(new Set([...tcpListenerSubscribedKeys.keyDown, ...webSocketListenerSubscribedKeys.keyDown])),
keyUp: Array.from(new Set([...tcpListenerSubscribedKeys.keyUp, ...webSocketListenerSubscribedKeys.keyUp])),
};
Main.emitter.emit('event_subscribed_keys_update', subscribeData);
}
});
l.start();

View file

@ -15,6 +15,21 @@ export enum RemoteKeyCode {
Back = 461,
}
export enum KeyCode {
ArrowUp = 38,
ArrowDown = 40,
ArrowLeft = 37,
ArrowRight = 39,
KeyK = 75,
Space = 32,
Enter = 13,
}
export enum ControlBarMode {
KeyboardMouse,
Remote
}
export class ServiceManager {
private static serviceChannelSuccessCbHandler?: (message: any) => void;
private static serviceChannelFailureCbHandler?: (message: any) => void;

View file

@ -48,7 +48,8 @@ window.webOSApp = {
keyUpEventHandler = callback;
document.addEventListener('keyup', keyUpEventHandler);
},
loadPage: loadPage
loadPage: loadPage,
pendingPlay: null,
};
document.addEventListener('webOSLaunch', launchHandlerCallback);

View file

@ -104,6 +104,12 @@ try {
});
});
window.targetAPI.initializeSubscribedKeys(() => {
return new Promise((resolve, reject) => {
serviceManager.call('get_subscribed_keys', {}, (message: any) => resolve(message.value), (message: any) => reject(message));
});
});
preloadData.sendEventCb = (event: EventMessage) => {
serviceManager.call('send_event', event, null, (message: any) => { logger.error(`Player: send_event ${JSON.stringify(message)}`); });
};

View file

@ -10,7 +10,6 @@ require('lib/webOSTVjs-1.2.10/webOSTV-dev.js');
declare global {
interface Window {
targetAPI: any;
webOSAPI: any;
webOSApp: any;
}
}
@ -20,10 +19,8 @@ const logger = window.targetAPI.logger;
try {
initializeWindowSizeStylesheet();
window.webOSAPI = {
pendingPlay: JSON.parse(sessionStorage.getItem('playInfo'))
};
const contentViewer = window.webOSAPI.pendingPlay?.contentViewer;
window.parent.webOSApp.pendingPlay = JSON.parse(sessionStorage.getItem('playInfo'));
const contentViewer = window.parent.webOSApp.pendingPlay?.contentViewer;
const serviceManager: ServiceManager = window.parent.webOSApp.serviceManager;
serviceManager.subscribeToServiceChannel((message: any) => {
@ -34,12 +31,13 @@ try {
case 'play': {
if (contentViewer !== message.value.contentViewer) {
sessionStorage.setItem('playInfo', JSON.stringify(message.value));
window.parent.webOSApp.loadPage(`${message.value.contentViewer}/index.html`);
}
else {
if (message.value.rendererEvent === 'play-playlist') {
if (preloadData.onPlayCb === undefined) {
window.webOSAPI.pendingPlay = message.value;
window.parent.webOSApp.pendingPlay = message.value;
}
else {
preloadData.onPlayPlaylistCb(null, message.value.rendererMessage);
@ -47,7 +45,7 @@ try {
}
else {
if (preloadData.onPlayCb === undefined) {
window.webOSAPI.pendingPlay = message.value;
window.parent.webOSApp.pendingPlay = message.value;
}
else {
preloadData.onPlayCb(null, message.value.rendererMessage);
@ -125,6 +123,11 @@ try {
serviceManager.call('get_sessions', {}, (message: any) => resolve(message.value), (message: any) => reject(message));
});
});
window.targetAPI.initializeSubscribedKeys(() => {
return new Promise((resolve, reject) => {
serviceManager.call('get_subscribed_keys', {}, (message: any) => resolve(message.value), (message: any) => reject(message));
});
});
const launchHandler = () => {
// args don't seem to be passed in via event despite what documentation says...

View file

@ -1,81 +1,125 @@
import {
isLive,
onPlay,
onPlayPlaylist,
setPlaylistItem,
playerCtrlStateUpdate,
playlistIndex,
player,
uiHideTimer,
PlayerControlEvent,
playerCtrlCaptions,
playerCtrlDuration,
playerCtrlLiveBadge,
playerCtrlPosition,
playerCtrlProgressBar,
playerCtrlProgressBarBuffer,
playerCtrlProgressBarHandle,
playerCtrlProgressBarProgress,
playerCtrlStateUpdate,
playerCtrlVolumeBar,
playerCtrlVolumeBarHandle,
playerCtrlVolumeBarProgress,
videoCaptions,
formatDuration,
skipBack,
skipForward,
keyDownEventHandler,
keyUpEventHandler,
playerCtrlProgressBarHandle,
} from 'common/player/Renderer';
import { RemoteKeyCode } from 'lib/common';
import { KeyCode, RemoteKeyCode, ControlBarMode } from 'lib/common';
import * as common from 'lib/common';
const logger = window.targetAPI.logger;
const captionsBaseHeightCollapsed = 150;
const captionsBaseHeightExpanded = 320;
const captionsLineHeight = 68;
const playPreviousContainer = document.getElementById('playPreviousContainer');
const actionContainer = document.getElementById('actionContainer');
const playNextContainer = document.getElementById('playNextContainer');
const playPrevious = document.getElementById('playPrevious');
const playNext = document.getElementById('playNext');
enum ControlFocus {
ProgressBar,
Action,
PlayPrevious,
PlayNext,
}
let controlMode = ControlBarMode.KeyboardMouse;
let controlFocus = ControlFocus.ProgressBar;
// Hide
// [<<][>][>>]
// [|<][>][>|]
// Hide
let locationMap = {
ProgressBar: playerCtrlProgressBarHandle,
Action: actionContainer,
PlayPrevious: playPreviousContainer,
PlayNext: playNextContainer,
};
window.parent.webOSApp.setKeyDownHandler(keyDownEventHandler);
window.parent.webOSApp.setKeyUpHandler(keyUpEventHandler);
uiHideTimer.setDelay(5000);
uiHideTimer.setCallback(() => {
if (!player?.isPaused()) {
controlMode = ControlBarMode.KeyboardMouse;
removeFocus(controlFocus);
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
}
});
// Leave control bar on screen if magic remote cursor leaves window
document.onmouseout = () => {
if (controlMode === ControlBarMode.KeyboardMouse) {
uiHideTimer.end();
}
}
function addFocus(location: ControlFocus) {
if (location === ControlFocus.ProgressBar) {
locationMap[ControlFocus[location]].classList.remove('progressBarHandleHide');
}
else {
locationMap[ControlFocus[location]].classList.add('buttonFocus');
}
}
function removeFocus(location: ControlFocus) {
if (location === ControlFocus.ProgressBar) {
locationMap[ControlFocus[location]].classList.add('progressBarHandleHide');
}
else {
locationMap[ControlFocus[location]].classList.remove('buttonFocus');
}
}
function remoteNavigateTo(location: ControlFocus) {
// Issues with using standard focus, so manually managing styles
removeFocus(controlFocus);
controlFocus = location;
addFocus(controlFocus);
}
function setControlMode(mode: ControlBarMode, immediateHide: boolean = true) {
if (mode === ControlBarMode.KeyboardMouse) {
uiHideTimer.enable();
if (immediateHide) {
removeFocus(controlFocus);
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
}
else {
uiHideTimer.start();
}
}
else {
remoteNavigateTo(ControlFocus.ProgressBar);
playerCtrlStateUpdate(PlayerControlEvent.UiFadeIn);
uiHideTimer.start();
}
controlMode = mode;
}
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
let handledCase = false;
switch (event) {
case PlayerControlEvent.Load: {
playerCtrlProgressBarBuffer.setAttribute("style", "width: 0px");
playerCtrlProgressBarProgress.setAttribute("style", "width: 0px");
playerCtrlProgressBarHandle.setAttribute("style", `left: ${playerCtrlProgressBar.offsetLeft}px`);
const volume = Math.round(player.getVolume() * playerCtrlVolumeBar.offsetWidth);
playerCtrlVolumeBarProgress.setAttribute("style", `width: ${volume}px`);
playerCtrlVolumeBarHandle.setAttribute("style", `left: ${volume + 8}px`);
if (isLive) {
playerCtrlLiveBadge.setAttribute("style", "display: block");
playerCtrlPosition.setAttribute("style", "display: none");
playerCtrlDuration.setAttribute("style", "display: none");
}
else {
playerCtrlLiveBadge.setAttribute("style", "display: none");
playerCtrlPosition.setAttribute("style", "display: block");
playerCtrlDuration.setAttribute("style", "display: block");
playerCtrlPosition.textContent = formatDuration(player.getCurrentTime());
playerCtrlDuration.innerHTML = formatDuration(player.getDuration());
}
if (player.isCaptionsSupported()) {
// Disabling receiver captions control on TV players
playerCtrlCaptions.setAttribute("style", "display: none");
// playerCtrlCaptions.setAttribute("style", "display: block");
videoCaptions.setAttribute("style", "display: block");
}
else {
playerCtrlCaptions.setAttribute("style", "display: none");
videoCaptions.setAttribute("style", "display: none");
player.enableCaptions(false);
}
playerCtrlStateUpdate(PlayerControlEvent.SetCaptions);
handledCase = true;
break;
}
default:
break;
}
@ -83,20 +127,164 @@ export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean
return handledCase;
}
export function targetPlayerCtrlPostStateUpdate(event: PlayerControlEvent) {
switch (event) {
case PlayerControlEvent.Load: {
player.setPlayPauseCallback(() => {
uiHideTimer.enable();
uiHideTimer.start();
}, () => {
uiHideTimer.disable();
});
if (player.isCaptionsSupported()) {
// Disabling receiver captions control on TV players
// playerCtrlCaptions.style.display = 'block';
playerCtrlCaptions.style.display = 'none';
videoCaptions.style.display = 'block';
}
else {
playerCtrlCaptions.style.display = 'none';
videoCaptions.style.display = 'none';
player.enableCaptions(false);
}
break;
}
default:
break;
}
}
export function targetKeyDownEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
// logger.info("KeyDown", event.keyCode);
let handledCase = false;
let key = '';
switch (event.keyCode) {
case KeyCode.KeyK:
case KeyCode.Space:
// Play/pause toggle
if (player?.isPaused()) {
player?.play();
} else {
player?.pause();
}
event.preventDefault();
handledCase = true;
break;
case KeyCode.Enter:
if (controlMode === ControlBarMode.KeyboardMouse) {
setControlMode(ControlBarMode.Remote);
}
else {
if (controlFocus === ControlFocus.ProgressBar || controlFocus === ControlFocus.Action) {
// Play/pause toggle
if (player?.isPaused()) {
player?.play();
} else {
player?.pause();
}
}
else if (controlFocus === ControlFocus.PlayPrevious) {
setPlaylistItem(playlistIndex - 1);
}
else if (controlFocus === ControlFocus.PlayNext) {
setPlaylistItem(playlistIndex + 1);
}
}
event.preventDefault();
handledCase = true;
break;
case KeyCode.ArrowUp:
if (controlMode === ControlBarMode.KeyboardMouse) {
setControlMode(ControlBarMode.Remote);
}
else {
if (controlFocus === ControlFocus.ProgressBar) {
setControlMode(ControlBarMode.KeyboardMouse);
}
else {
remoteNavigateTo(ControlFocus.ProgressBar);
}
}
event.preventDefault();
handledCase = true;
break;
case KeyCode.ArrowDown:
if (controlMode === ControlBarMode.KeyboardMouse) {
setControlMode(ControlBarMode.Remote);
}
else {
if (controlFocus === ControlFocus.ProgressBar) {
remoteNavigateTo(ControlFocus.Action);
}
else {
setControlMode(ControlBarMode.KeyboardMouse);
}
}
event.preventDefault();
handledCase = true;
break;
case KeyCode.ArrowLeft:
if (controlMode === ControlBarMode.KeyboardMouse) {
setControlMode(ControlBarMode.Remote);
}
else {
if (controlFocus === ControlFocus.ProgressBar || playPrevious?.style.display === 'none') {
// Note that skip repeat does not trigger in simulator
skipBack(event.repeat);
}
else {
if (controlFocus === ControlFocus.Action) {
remoteNavigateTo(ControlFocus.PlayPrevious);
}
else if (controlFocus === ControlFocus.PlayNext) {
remoteNavigateTo(ControlFocus.Action);
}
}
}
event.preventDefault();
handledCase = true;
break;
case KeyCode.ArrowRight:
if (controlMode === ControlBarMode.KeyboardMouse) {
setControlMode(ControlBarMode.Remote);
}
else {
if (controlFocus === ControlFocus.ProgressBar || playNext?.style.display === 'none') {
// Note that skip repeat does not trigger in simulator
skipForward(event.repeat);
}
else {
if (controlFocus === ControlFocus.Action) {
remoteNavigateTo(ControlFocus.PlayNext);
}
else if (controlFocus === ControlFocus.PlayPrevious) {
remoteNavigateTo(ControlFocus.Action);
}
}
}
event.preventDefault();
handledCase = true;
break;
case RemoteKeyCode.Stop:
// history.back();
window.open('../main_window/index.html', '_self');
window.parent.webOSApp.loadPage('main_window/index.html');
handledCase = true;
key = 'Stop';
break;
// Note that in simulator rewind and fast forward key codes are sent twice...
case RemoteKeyCode.Rewind:
skipBack();
skipBack(event.repeat);
event.preventDefault();
handledCase = true;
key = 'Rewind';
@ -119,18 +307,16 @@ export function targetKeyDownEventListener(event: KeyboardEvent): { handledCase:
key = 'Pause';
break;
// Note that in simulator rewind and fast forward key codes are sent twice...
case RemoteKeyCode.FastForward:
skipForward();
skipForward(event.repeat);
event.preventDefault();
handledCase = true;
key = 'FastForward';
break;
// WebOS 22 and earlier does not work well using the history API,
// so manually handling page navigation...
case RemoteKeyCode.Back:
// history.back();
window.open('../main_window/index.html', '_self');
window.parent.webOSApp.loadPage('main_window/index.html');
event.preventDefault();
handledCase = true;
key = 'Back';
@ -147,12 +333,12 @@ export function targetKeyUpEventListener(event: KeyboardEvent): { handledCase: b
return common.targetKeyUpEventListener(event);
};
if (window.webOSAPI.pendingPlay !== null) {
if (window.webOSAPI.pendingPlay.rendererEvent === 'play-playlist') {
onPlayPlaylist(null, window.webOSAPI.pendingPlay.rendererMessage);
if (window.parent.webOSApp.pendingPlay !== null) {
if (window.parent.webOSApp.pendingPlay.rendererEvent === 'play-playlist') {
onPlayPlaylist(null, window.parent.webOSApp.pendingPlay.rendererMessage);
}
else {
onPlay(null, window.webOSAPI.pendingPlay.rendererMessage);
onPlay(null, window.parent.webOSApp.pendingPlay.rendererMessage);
}
}

View file

@ -25,7 +25,7 @@
<div id="progressBarProgress" class="progressBarProgress" ></div>
<div id="progressBarPosition" class="progressBarPosition" ></div>
<!-- <div class="progressBarChapterContainer"></div> -->
<div id="progressBarHandle" class="progressBarHandle" ></div>
<div id="progressBarHandle" class="progressBarHandle progressBarHandleHide" ></div>
<div id="progressBarInteractiveArea" class="progressBarInteractiveArea" ></div>
</div>
@ -37,9 +37,9 @@
</div>
<div class="leftButtonContainer">
<div id="playPrevious" class="playPrevious iconSize"></div>
<div id="action" class="play iconSize"></div>
<div id="playNext" class="playNext iconSize"></div>
<div id="playPreviousContainer" class="buttonFocusContainer"><div id="playPrevious" class="playPrevious iconSize"></div></div>
<div id="actionContainer" class="buttonFocusContainer"><div id="action" class="play iconSize"></div></div>
<div id="playNextContainer" class="buttonFocusContainer"><div id="playNext" class="playNext iconSize"></div></div>
<div id="volume" class="volume_high iconSize"></div>
<div class="volumeContainer">
@ -59,7 +59,7 @@
<div class="buttonContainer">
<!-- <div id="fullscreen" class="fullscreen_on iconSize"></div> -->
<div id="speed" class="speed iconSize"></div>
<div id="captions" class="captions_off iconSize"></div>
<div id="captions" class="captions_off iconSize" style="display: none"></div>
<div id="duration" class="duration">00:00</div>
</div>

View file

@ -6,6 +6,7 @@ html {
.container {
height: 240px;
background: linear-gradient(to top, rgba(0, 0, 0, 1.0) 0%, rgba(0, 0, 0, 0.0) 80%);
}
.iconSize {
@ -19,39 +20,9 @@ html {
}
.volumeContainer {
height: 48px;
width: 184px;
display: none;
}
.volumeBar {
left: 16px;
top: 20px;
height: 8px;
width: 152px;
}
.volumeBarInteractiveArea {
height: 48px;
width: 184px;
}
.volumeBarHandle {
left: 168px;
top: 8px;
width: 32px;
height: 32px;
box-shadow: 0px 64px 128px 0px rgba(0, 0, 0, 0.56), 0px 4px 42px 0px rgba(0, 0, 0, 0.55);
}
.volumeBarProgress {
left: 16px;
top: 20px;
height: 8px;
width: 152px;
}
.progressBarContainer {
bottom: 120px;
left: 32px;
@ -138,21 +109,42 @@ html {
}
.leftButtonContainer {
bottom: 48px;
bottom: 24px;
left: 48px;
height: 48px;
/* right: 320px; */
right: 32px;
gap: 48px;
height: 96px;
right: 48px;
/* gap: 48px; */
gap: unset;
justify-content: center;
}
.buttonFocusContainer {
margin-right: 28px;
padding: 20px;
border-radius: 20px;
}
.buttonFocus {
/* background-image: linear-gradient(to bottom, #008BD7 35%, #0069AA); */
background-image: linear-gradient(to bottom, #808080 35%, #202020);
border: 1px solid #4E4E4E;
}
.progressBarHandle {
border: 1px solid #4E4E4E;
}
.progressBarHandleHide {
display: none;
}
.buttonContainer {
bottom: 48px;
right: 48px;
height: 48px;
gap: 48px;
/* gap: 48px; */
gap: unset;
}
.captionsContainer {

View file

@ -1,60 +1,274 @@
import { PlayerControlEvent, playerCtrlStateUpdate, onPlay, onPlayPlaylist, setPlaylistItem, playlistIndex, keyDownEventHandler, keyUpEventHandler } from 'common/viewer/Renderer';
import { RemoteKeyCode } from 'lib/common';
import {
PlayerControlEvent,
playerCtrlStateUpdate,
onPlay,
onPlayPlaylist,
setPlaylistItem,
playlistIndex,
uiHideTimer,
showDurationTimer,
isMediaItem,
cachedPlayMediaItem,
imageViewerPlaybackState,
keyDownEventHandler,
keyUpEventHandler
} from 'common/viewer/Renderer';
import { KeyCode, RemoteKeyCode, ControlBarMode } from 'lib/common';
import * as common from 'lib/common';
import { PlaybackState } from 'common/Packets';
const logger = window.targetAPI.logger;
const playPreviousContainer = document.getElementById('playPreviousContainer');
const actionContainer = document.getElementById('actionContainer');
const playNextContainer = document.getElementById('playNextContainer');
const action = document.getElementById('action');
enum ControlFocus {
Action,
PlayPrevious,
PlayNext,
}
let controlMode = ControlBarMode.KeyboardMouse;
let controlFocus = ControlFocus.Action;
// Hide
// [|<][>][>|]
// Hide
let locationMap = {
Action: actionContainer,
PlayPrevious: playPreviousContainer,
PlayNext: playNextContainer,
};
window.parent.webOSApp.setKeyDownHandler(keyDownEventHandler);
window.parent.webOSApp.setKeyUpHandler(keyUpEventHandler);
uiHideTimer.setDelay(5000);
uiHideTimer.setCallback(() => {
if (controlMode === ControlBarMode.KeyboardMouse || !showDurationTimer.isPaused()) {
controlMode = ControlBarMode.KeyboardMouse;
locationMap[ControlFocus[controlFocus]].classList.remove('buttonFocus');
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
}
});
// Leave control bar on screen if magic remote cursor leaves window
document.onmouseout = () => {
if (controlMode === ControlBarMode.KeyboardMouse) {
uiHideTimer.end();
}
}
function remoteNavigateTo(location: ControlFocus) {
// Issues with using standard focus, so manually managing styles
locationMap[ControlFocus[controlFocus]].classList.remove('buttonFocus');
controlFocus = location;
locationMap[ControlFocus[controlFocus]].classList.add('buttonFocus');
}
function setControlMode(mode: ControlBarMode, immediateHide: boolean = true) {
if (mode === ControlBarMode.KeyboardMouse) {
uiHideTimer.enable();
if (immediateHide) {
locationMap[ControlFocus[controlFocus]].classList.remove('buttonFocus');
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
}
else {
uiHideTimer.start();
}
}
else {
const focus = action?.style.display === 'none' ? ControlFocus.PlayNext : ControlFocus.Action;
remoteNavigateTo(focus);
playerCtrlStateUpdate(PlayerControlEvent.UiFadeIn);
uiHideTimer.start();
}
controlMode = mode;
}
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
let handledCase = false;
switch (event) {
default:
break;
}
return handledCase;
}
export function targetPlayerCtrlPostStateUpdate(event: PlayerControlEvent) {
switch (event) {
case PlayerControlEvent.Load: {
if (!isMediaItem && controlMode === ControlBarMode.Remote) {
setControlMode(ControlBarMode.KeyboardMouse);
}
if (action?.style.display === 'none') {
actionContainer.style.display = 'none';
}
else {
actionContainer.style.display = 'block';
}
break;
}
default:
break;
}
}
export function targetKeyDownEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
let handledCase = false;
let key = '';
switch (event.keyCode) {
case KeyCode.KeyK:
case KeyCode.Space:
if (isMediaItem) {
// Play/pause toggle
if (cachedPlayMediaItem.showDuration && cachedPlayMediaItem.showDuration > 0) {
if (imageViewerPlaybackState === PlaybackState.Paused || imageViewerPlaybackState === PlaybackState.Idle) {
playerCtrlStateUpdate(PlayerControlEvent.Play);
} else {
playerCtrlStateUpdate(PlayerControlEvent.Pause);
}
}
event.preventDefault();
handledCase = true;
}
break;
case KeyCode.Enter:
if (isMediaItem) {
if (controlMode === ControlBarMode.KeyboardMouse) {
setControlMode(ControlBarMode.Remote);
}
else {
if (controlFocus === ControlFocus.Action) {
// Play/pause toggle
if (cachedPlayMediaItem.showDuration && cachedPlayMediaItem.showDuration > 0) {
if (imageViewerPlaybackState === PlaybackState.Paused || imageViewerPlaybackState === PlaybackState.Idle) {
playerCtrlStateUpdate(PlayerControlEvent.Play);
} else {
playerCtrlStateUpdate(PlayerControlEvent.Pause);
}
}
}
else if (controlFocus === ControlFocus.PlayPrevious) {
setPlaylistItem(playlistIndex - 1);
}
else if (controlFocus === ControlFocus.PlayNext) {
setPlaylistItem(playlistIndex + 1);
}
}
event.preventDefault();
handledCase = true;
}
break;
case KeyCode.ArrowUp:
case KeyCode.ArrowDown:
if (isMediaItem) {
if (controlMode === ControlBarMode.KeyboardMouse) {
setControlMode(ControlBarMode.Remote);
}
else {
setControlMode(ControlBarMode.KeyboardMouse);
}
event.preventDefault();
handledCase = true;
}
break;
case KeyCode.ArrowLeft:
if (isMediaItem) {
if (controlMode === ControlBarMode.KeyboardMouse) {
setPlaylistItem(playlistIndex - 1);
}
else {
if (controlFocus === ControlFocus.Action || action?.style.display === 'none') {
remoteNavigateTo(ControlFocus.PlayPrevious);
}
else if (controlFocus === ControlFocus.PlayNext) {
remoteNavigateTo(ControlFocus.Action);
}
}
event.preventDefault();
handledCase = true;
}
break;
case KeyCode.ArrowRight:
if (isMediaItem) {
if (controlMode === ControlBarMode.KeyboardMouse) {
setPlaylistItem(playlistIndex + 1);
}
else {
if (controlFocus === ControlFocus.Action || action?.style.display === 'none') {
remoteNavigateTo(ControlFocus.PlayNext);
}
else if (controlFocus === ControlFocus.PlayPrevious) {
remoteNavigateTo(ControlFocus.Action);
}
}
event.preventDefault();
handledCase = true;
}
break;
case RemoteKeyCode.Stop:
// history.back();
window.open('../main_window/index.html', '_self');
window.parent.webOSApp.loadPage('main_window/index.html');
event.preventDefault();
handledCase = true;
key = 'Stop';
break;
// Note that in simulator rewind and fast forward key codes are sent twice...
case RemoteKeyCode.Rewind:
setPlaylistItem(playlistIndex - 1);
event.preventDefault();
handledCase = true;
key = 'Rewind';
if (isMediaItem) {
setPlaylistItem(playlistIndex - 1);
event.preventDefault();
handledCase = true;
key = 'Rewind';
}
break;
case RemoteKeyCode.Play:
playerCtrlStateUpdate(PlayerControlEvent.Play);
event.preventDefault();
handledCase = true;
key = 'Play';
if (isMediaItem) {
playerCtrlStateUpdate(PlayerControlEvent.Play);
event.preventDefault();
handledCase = true;
key = 'Play';
}
break;
case RemoteKeyCode.Pause:
playerCtrlStateUpdate(PlayerControlEvent.Pause);
event.preventDefault();
handledCase = true;
key = 'Pause';
if (isMediaItem) {
playerCtrlStateUpdate(PlayerControlEvent.Pause);
event.preventDefault();
handledCase = true;
key = 'Pause';
}
break;
// Note that in simulator rewind and fast forward key codes are sent twice...
case RemoteKeyCode.FastForward:
setPlaylistItem(playlistIndex + 1);
event.preventDefault();
handledCase = true;
key = 'FastForward';
if (isMediaItem) {
setPlaylistItem(playlistIndex + 1);
event.preventDefault();
handledCase = true;
key = 'FastForward';
}
break;
// WebOS 22 and earlier does not work well using the history API,
// so manually handling page navigation...
case RemoteKeyCode.Back:
// history.back();
window.open('../main_window/index.html', '_self');
window.parent.webOSApp.loadPage('main_window/index.html');
event.preventDefault();
handledCase = true;
key = 'Back';
@ -71,11 +285,11 @@ export function targetKeyUpEventListener(event: KeyboardEvent): { handledCase: b
return common.targetKeyUpEventListener(event);
};
if (window.webOSAPI.pendingPlay !== null) {
if (window.webOSAPI.pendingPlay.rendererEvent === 'play-playlist') {
onPlayPlaylist(null, window.webOSAPI.pendingPlay.rendererMessage);
if (window.parent.webOSApp.pendingPlay !== null) {
if (window.parent.webOSApp.pendingPlay.rendererEvent === 'play-playlist') {
onPlayPlaylist(null, window.parent.webOSApp.pendingPlay.rendererMessage);
}
else {
onPlay(null, window.webOSAPI.pendingPlay.rendererMessage);
onPlay(null, window.parent.webOSApp.pendingPlay.rendererMessage);
}
}

View file

@ -24,10 +24,10 @@
<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="playPreviousContainer" class="buttonFocusContainer"><div id="playPrevious" class="playPrevious iconSize" style="display: none"></div></div>
<div id="actionContainer" class="buttonFocusContainer"><div id="action" class="play iconSize" style="display: none"></div></div>
<div id="playlistLength" style="display: none"></div>
<div id="playNext" class="playNext iconSize" style="display: none"></div>
<div id="playNextContainer" class="buttonFocusContainer"><div id="playNext" class="playNext iconSize" style="display: none"></div></div>
</div>
<!-- <div id="rightButtonContainer" class="buttonContainer">

View file

@ -4,6 +4,52 @@ html {
overflow: hidden;
}
.container {
height: 140px;
background: linear-gradient(to top, rgba(0, 0, 0, 1.0) 0%, rgba(0, 0, 0, 0.0) 100%);
}
.iconSize {
width: 48px;
height: 48px;
background-size: cover;
}
#leftButtonContainer {
left: 48px;
font-family: InterRegular;
font-size: 28px;
}
#centerButtonContainer {
font-family: InterRegular;
font-size: 28px;
}
#playlistLength {
margin-right: 28px;
}
.buttonContainer {
bottom: 24px;
height: 96px;
/* gap: 48px; */
gap: unset;
}
.buttonFocusContainer {
margin-right: 28px;
padding: 20px;
border-radius: 20px;
}
.buttonFocus {
/* background-image: linear-gradient(to bottom, #008BD7 35%, #0069AA); */
background-image: linear-gradient(to bottom, #808080 35%, #202020);
border: 1px solid #4E4E4E;
}
#toast-notification {
gap: unset;
}