1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-06-24 21:25:23 +00:00

Finished initial WebOS receiver implementation

This commit is contained in:
Michael Hollister 2024-12-17 00:10:12 -06:00
parent 41f80880e4
commit 2df64dca89
20 changed files with 457 additions and 92 deletions

View file

@ -1,6 +1,6 @@
import * as net from 'net';
import * as log4js from "modules/log4js";
import { EventEmitter } from 'node:events';
import { EventEmitter } from 'events';
import { PlaybackErrorMessage, PlaybackUpdateMessage, PlayMessage, SeekMessage, SetSpeedMessage, SetVolumeMessage, VersionMessage, VolumeUpdateMessage } from 'common/Packets';
import { WebSocket } from 'modules/ws';
const logger = log4js.getLogger();

View file

@ -1,6 +1,6 @@
import * as net from 'net';
import { FCastSession, Opcode } from 'common/FCastSession';
import { EventEmitter } from 'node:events';
import { EventEmitter } from 'events';
import { Main, errorHandler } from 'src/Main';
export class TcpListenerService {

View file

@ -1,5 +1,5 @@
import { FCastSession, Opcode } from 'common/FCastSession';
import { EventEmitter } from 'node:events';
import { EventEmitter } from 'events';
import { WebSocket, WebSocketServer } from 'modules/ws';
import { Main, errorHandler } from 'src/Main';

View file

@ -86,6 +86,7 @@ if (TARGET === 'electron') {
playService.cancel();
}
history.pushState({}, '', '../main_window/index.html');
window.open('../player/index.html');
}
});

View file

@ -1,5 +1,6 @@
import QRCode from 'modules/qrcode';
import { onQRCodeRendered } from 'src/main/Renderer';
window.targetAPI.onDeviceInfo(renderIPsAndQRCode);
@ -45,4 +46,6 @@ function renderIPsAndQRCode() {
(e) => {
console.log(`Error rendering QR Code: ${e}`)
});
onQRCodeRendered();
}

View file

@ -123,7 +123,7 @@ if (TARGET === 'electron') {
setSpeedService.cancel();
// window.open('../main_window/index.html');
window.webOS.platformBack();
history.back();
}
},
onFailure: (message: any) => {

View file

@ -2,7 +2,13 @@ import dashjs from 'modules/dashjs';
import Hls, { LevelLoadedData } from 'modules/hls.js';
import { PlaybackUpdateMessage, PlayMessage, SeekMessage, SetSpeedMessage, SetVolumeMessage } from 'common/Packets';
import { Player, PlayerType } from './Player';
import { targetPlayerCtrlStateUpdate, targetKeyDownEventListener } from 'src/player/Renderer';
import {
targetPlayerCtrlStateUpdate,
targetKeyDownEventListener,
captionsBaseHeightCollapsed,
captionsBaseHeightExpanded,
captionsLineHeight
} from 'src/player/Renderer';
function formatDuration(duration: number) {
const totalSeconds = Math.floor(duration);
@ -85,6 +91,7 @@ const playerCtrlVolumeBarInteractiveArea = document.getElementById("volumeBarInt
const playerCtrlLiveBadge = document.getElementById("liveBadge");
const playerCtrlPosition = document.getElementById("position");
const playerCtrlDurationSeparator = document.getElementById("durationSeparator");
const playerCtrlDuration = document.getElementById("duration");
const playerCtrlCaptions = document.getElementById("captions");
@ -103,7 +110,7 @@ let playerPrevTime: number = 0;
let lastPlayerUpdateGenerationTime = 0;
let isLive = false;
let isLivePosition = false;
let captionsBaseHeight = 160;
let captionsBaseHeight = 0;
let captionsContentHeight = 0;
function onPlay(_event, value: PlayMessage) {
@ -115,6 +122,7 @@ function onPlay(_event, value: PlayMessage) {
lastPlayerUpdateGenerationTime = 0;
isLive = false;
isLivePosition = false;
captionsBaseHeight = captionsBaseHeightExpanded;
if (player) {
if (player.getSource() === value.url) {
@ -193,7 +201,7 @@ function onPlay(_event, value: PlayMessage) {
subtitle.textContent = e.text;
videoCaptions.appendChild(subtitle);
captionsContentHeight = subtitle.getBoundingClientRect().height - 34;
captionsContentHeight = subtitle.getBoundingClientRect().height - captionsLineHeight;
const captionsHeight = captionsBaseHeight + captionsContentHeight;
if (player.isCaptionsEnabled()) {
@ -293,7 +301,17 @@ function onPlay(_event, value: PlayMessage) {
console.error("Player error", {source, lineno, colno, error});
};
videoElement.onloadedmetadata = () => { onPlayerLoad(value, currentPlaybackRate, currentVolume); };
videoElement.onloadedmetadata = (ev) => {
if (videoElement.duration === Infinity) {
isLive = true;
isLivePosition = true;
}
else {
isLive = false;
isLivePosition = false;
}
onPlayerLoad(value, currentPlaybackRate, currentVolume); };
}
}
@ -327,6 +345,11 @@ enum PlayerControlEvent {
// UI update handlers
function playerCtrlStateUpdate(event: PlayerControlEvent) {
const handledCase = targetPlayerCtrlStateUpdate(event);
if (handledCase) {
return;
}
switch (event) {
case PlayerControlEvent.Load: {
playerCtrlProgressBarBuffer.setAttribute("style", "width: 0px");
@ -340,14 +363,16 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
if (isLive) {
playerCtrlLiveBadge.setAttribute("style", "display: block");
playerCtrlPosition.setAttribute("style", "display: none");
playerCtrlDurationSeparator.setAttribute("style", "display: none");
playerCtrlDuration.setAttribute("style", "display: none");
}
else {
playerCtrlLiveBadge.setAttribute("style", "display: none");
playerCtrlPosition.setAttribute("style", "display: block");
playerCtrlDurationSeparator.setAttribute("style", "display: block");
playerCtrlDuration.setAttribute("style", "display: block");
playerCtrlPosition.textContent = formatDuration(player.getCurrentTime());
playerCtrlDuration.innerHTML = `/&nbsp&nbsp${formatDuration(player.getDuration())}`;
playerCtrlDuration.innerHTML = formatDuration(player.getDuration());
}
if (player.isCaptionsSupported()) {
@ -364,12 +389,12 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
}
case PlayerControlEvent.Pause:
playerCtrlAction.setAttribute("class", "play");
playerCtrlAction.setAttribute("class", "play iconSize");
stopUiHideTimer();
break;
case PlayerControlEvent.Play:
playerCtrlAction.setAttribute("class", "pause");
playerCtrlAction.setAttribute("class", "pause iconSize");
startUiHideTimer();
break;
@ -378,16 +403,16 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
const volume = Math.round(player.getVolume() * playerCtrlVolumeBar.offsetWidth);
if (player.isMuted()) {
playerCtrlVolume.setAttribute("class", "mute");
playerCtrlVolume.setAttribute("class", "mute iconSize");
playerCtrlVolumeBarProgress.setAttribute("style", `width: 0px`);
playerCtrlVolumeBarHandle.setAttribute("style", `left: 0px`);
}
else if (player.getVolume() >= 0.5) {
playerCtrlVolume.setAttribute("class", "volume_high");
playerCtrlVolume.setAttribute("class", "volume_high iconSize");
playerCtrlVolumeBarProgress.setAttribute("style", `width: ${volume}px`);
playerCtrlVolumeBarHandle.setAttribute("style", `left: ${volume}px`);
} else {
playerCtrlVolume.setAttribute("class", "volume_low");
playerCtrlVolume.setAttribute("class", "volume_low iconSize");
playerCtrlVolumeBarProgress.setAttribute("style", `width: ${volume}px`);
playerCtrlVolumeBarHandle.setAttribute("style", `left: ${volume}px`);
}
@ -430,7 +455,7 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
case PlayerControlEvent.UiFadeOut: {
document.body.style.cursor = "none";
playerControls.setAttribute("style", "opacity: 0");
captionsBaseHeight = 75;
captionsBaseHeight = captionsBaseHeightCollapsed;
const captionsHeight = captionsBaseHeight + captionsContentHeight;
if (player.isCaptionsEnabled()) {
@ -446,7 +471,7 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
case PlayerControlEvent.UiFadeIn: {
document.body.style.cursor = "default";
playerControls.setAttribute("style", "opacity: 1");
captionsBaseHeight = 160;
captionsBaseHeight = captionsBaseHeightExpanded;
const captionsHeight = captionsBaseHeight + captionsContentHeight;
if (player.isCaptionsEnabled()) {
@ -460,10 +485,10 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
case PlayerControlEvent.SetCaptions:
if (player.isCaptionsEnabled()) {
playerCtrlCaptions.setAttribute("class", "captions_on");
playerCtrlCaptions.setAttribute("class", "captions_on iconSize");
videoCaptions.setAttribute("style", "display: block");
} else {
playerCtrlCaptions.setAttribute("class", "captions_off");
playerCtrlCaptions.setAttribute("class", "captions_off iconSize");
videoCaptions.setAttribute("style", "display: none");
}
@ -498,14 +523,13 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
}
default:
targetPlayerCtrlStateUpdate(event);
break;
}
}
function scrubbingMouseUIHandler(e: MouseEvent) {
const progressBarOffset = e.offsetX - 8;
const progressBarWidth = PlayerCtrlProgressBarInteractiveArea.offsetWidth - 16;
const progressBarOffset = e.offsetX - playerCtrlProgressBar.offsetLeft;
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));
@ -546,8 +570,8 @@ PlayerCtrlProgressBarInteractiveArea.onmouseleave = () => { playerCtrlProgressBa
PlayerCtrlProgressBarInteractiveArea.onmousemove = (e: MouseEvent) => { scrubbingMouseHandler(e) };
function scrubbingMouseHandler(e: MouseEvent) {
const progressBarOffset = e.offsetX - 8;
const progressBarWidth = PlayerCtrlProgressBarInteractiveArea.offsetWidth - 16;
const progressBarOffset = e.offsetX - playerCtrlProgressBar.offsetLeft;
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));
@ -578,8 +602,8 @@ playerCtrlVolumeBarInteractiveArea.onwheel = (e: WheelEvent) => {
function volumeChangeMouseHandler(e: MouseEvent) {
if (volumeChanging && e.buttons === 1) {
const volumeBarOffsetX = e.offsetX - 8;
const volumeBarWidth = playerCtrlVolumeBarInteractiveArea.offsetWidth - 16;
const volumeBarOffsetX = e.offsetX - playerCtrlVolumeBar.offsetLeft;
const volumeBarWidth = playerCtrlVolumeBarInteractiveArea.offsetWidth - (playerCtrlVolumeBar.offsetLeft * 2);
const volume = volumeBarOffsetX / volumeBarWidth;
volumeChangeHandler(volume);
}
@ -761,8 +785,25 @@ function keyDownEventListener(event: any) {
document.addEventListener('keydown', keyDownEventListener);
export {
videoElement,
PlayerControlEvent,
videoElement,
videoCaptions,
playerCtrlProgressBar,
playerCtrlProgressBarBuffer,
playerCtrlProgressBarProgress,
playerCtrlProgressBarHandle,
playerCtrlVolumeBar,
playerCtrlVolumeBarProgress,
playerCtrlVolumeBarHandle,
playerCtrlLiveBadge,
playerCtrlPosition,
playerCtrlDuration,
playerCtrlCaptions,
player,
isLive,
captionsBaseHeight,
captionsLineHeight,
onPlay,
playerCtrlStateUpdate,
formatDuration,
};

View file

@ -44,6 +44,11 @@ body {
transition: opacity 0.1s ease-in-out;
}
.iconSize {
width: 24px;
height: 24px;
}
.volumeContainer {
position: relative;
height: 24px;
@ -252,8 +257,6 @@ body {
}
.play {
width: 24px;
height: 24px;
cursor: pointer;
flex-shrink: 0;
@ -266,8 +269,6 @@ body {
}
.pause {
width: 24px;
height: 24px;
cursor: pointer;
flex-shrink: 0;
@ -280,8 +281,6 @@ body {
}
.volume_high {
width: 24px;
height: 24px;
cursor: pointer;
flex-shrink: 0;
@ -294,8 +293,6 @@ body {
}
.volume_low {
width: 24px;
height: 24px;
cursor: pointer;
flex-shrink: 0;
@ -308,8 +305,6 @@ body {
}
.mute {
width: 24px;
height: 24px;
cursor: pointer;
flex-shrink: 0;
@ -322,8 +317,6 @@ body {
}
.speed {
width: 24px;
height: 24px;
cursor: pointer;
background-image: url("../assets/icons/player/icon24_speed.svg");
@ -335,8 +328,6 @@ body {
}
.captions_off {
width: 24px;
height: 24px;
cursor: pointer;
background-image: url("../assets/icons/player/icon24_cc_off.svg");
@ -348,8 +339,6 @@ body {
}
.captions_on {
width: 24px;
height: 24px;
cursor: pointer;
background-image: url("../assets/icons/player/icon24_cc_on.svg");

View file

@ -1,5 +1,7 @@
import 'common/main/Renderer';
export function onQRCodeRendered() {}
const updateView = document.getElementById("update-view");
const updateViewTitle = document.getElementById("update-view-title");
const updateText = document.getElementById("update-text");

View file

@ -1,10 +1,16 @@
import { videoElement, PlayerControlEvent, playerCtrlStateUpdate } from 'common/player/Renderer';
const captionsBaseHeightCollapsed = 75;
const captionsBaseHeightExpanded = 160;
const captionsLineHeight = 34;
const playerCtrlFullscreen = document.getElementById("fullscreen");
playerCtrlFullscreen.onclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
videoElement.ondblclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent) {
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
let handledCase = false;
switch (event) {
case PlayerControlEvent.ToggleFullscreen: {
window.electronAPI.toggleFullScreen();
@ -17,17 +23,22 @@ export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent) {
}
});
handledCase = true;
break;
}
case PlayerControlEvent.ExitFullscreen:
window.electronAPI.exitFullScreen();
playerCtrlFullscreen.setAttribute("class", "fullscreen_off");
handledCase = true;
break;
default:
break;
}
return handledCase;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -46,3 +57,9 @@ export function targetKeyDownEventListener(event: any) {
break;
}
};
export {
captionsBaseHeightCollapsed,
captionsBaseHeightExpanded,
captionsLineHeight,
}

View file

@ -23,9 +23,9 @@
</div>
<div class="leftButtonContainer">
<div id="action" class="play"></div>
<div id="action" class="play iconSize"></div>
<div id="volume" class="volume_high"></div>
<div id="volume" class="volume_high iconSize"></div>
<div class="volumeContainer">
<div id="volumeBar" ref="volumeBar" class="volumeBar" ></div>
<div id="volumeBarProgress" class="volumeBarProgress" ></div>
@ -36,14 +36,15 @@
<div class="positionContainer">
<div id="liveBadge" class="liveBadge" style="display: none">LIVE</div>
<div id="position" class="position">00:00</div>
<div id="duration" class="duration">/&nbsp&nbsp00:00</div>
<div id="durationSeparator" class="duration">/&nbsp&nbsp</div>
<div id="duration" class="duration">00:00</div>
</div>
</div>
<div class="buttonContainer">
<div id="fullscreen" class="fullscreen_on"></div>
<div id="speed" class="speed"></div>
<div id="captions" class="captions_off"></div>
<div id="fullscreen" class="fullscreen_on iconSize"></div>
<div id="speed" class="speed iconSize"></div>
<div id="captions" class="captions_off iconSize"></div>
</div>
<div id="speedMenu" class="speedMenu" style="display: none">

View file

@ -13,7 +13,7 @@ import { NetworkService } from 'common/NetworkService';
import { Opcode } from 'common/FCastSession';
import * as os from 'os';
import * as log4js from "log4js";
import { EventEmitter } from 'node:events';
import { EventEmitter } from 'events';
export class Main {
static tcpListenerService: TcpListenerService;
@ -32,24 +32,16 @@ export class Main {
},
});
Main.logger = log4js.getLogger();
// Main.logger.info(`Starting application: ${app.name} | ${app.getAppPath()}`);
// Main.logger.info(`Version: ${app.getVersion()}`);
// Main.logger.info(`Commit: ${Updater.getCommit()}`);
// Main.logger.info(`Release channel: ${Updater.releaseChannel} - ${Updater.getChannelVersion()}`);
Main.logger.info(`OS: ${process.platform} ${process.arch}`);
const serviceId = 'com.futo.fcast.receiver.service';
const service = new Service(serviceId);
// let keepAlive;
// service.activityManager.create("keepAlive", function(activity) {
// keepAlive = activity;
// });
// // When you&#39;re done, complete the activity
// service.activityManager.complete(keepAlive, function(activity) {
// Main.logger.info("completed activity");
// });
// Service will timeout and casting will disconnect if not forced to be kept alive
let keepAlive;
service.activityManager.create("keepAlive", function(activity) {
keepAlive = activity;
});
service.register("getDeviceInfo", (message: any) => {
Main.logger.info("In getDeviceInfo callback");
@ -197,22 +189,14 @@ export class Main {
await NetworkService.proxyPlayIfRequired(message);
emitter.emit('play', message);
service.call("luna://com.webos.applicationManager/launch", { playData: message }, (_response) => {
console.log(`Relaunching FCast Receiver with args: ${JSON.stringify(message)}`);
const appId = 'com.futo.fcast.receiver';
service.call("luna://com.webos.applicationManager/launch", {
'id': appId,
'params': { playData: message }
}, (response: any) => {
Main.logger.info(`Launch response: ${JSON.stringify(response)}`);
Main.logger.info(`Relaunching FCast Receiver with args: ${JSON.stringify(message)}`);
});
// const appId = 'com.futo.fcast.receiver';
// window.webOSDev.launch({
// id: appId,
// params: {
// playData: message,
// },
// onSuccess: function () {
// console.log(`Service: Launching application...`);
// },
// onFailure: function (message: any) {
// console.error(`Service: launch ${JSON.stringify(message)}`);
// },
// });
});
l.emitter.on("pause", () => emitter.emit('pause'));
l.emitter.on("resume", () => emitter.emit('resume'));

View file

@ -1,8 +1,8 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "node",
"target": "ES2015",
"module": "ES2015",
"moduleResolution": "node10",
"sourceMap": false,
"emitDecoratorMetadata": true,
"esModuleInterop": true,

View file

@ -1 +1,6 @@
import 'common/main/Preload';
// Cannot go back to a state where user was previously casting a video, so exit.
window.onpopstate = () => {
window.webOS.platformBack();
};

View file

@ -1 +1,22 @@
import 'common/main/Renderer';
const backgroundVideo = document.getElementById('video-player');
const loadingScreen = document.getElementById('loading-screen');
let backgroundVideoLoaded = false;
let qrCodeRendered = false;
backgroundVideo.onplaying = () => {
backgroundVideoLoaded = true;
if (backgroundVideoLoaded && qrCodeRendered) {
loadingScreen.style.display = 'none';
}
};
export function onQRCodeRendered() {
qrCodeRendered = true;
if (backgroundVideoLoaded && qrCodeRendered) {
loadingScreen.style.display = 'none';
}
}

View file

@ -12,6 +12,10 @@
</head>
<body>
<div id="loading-screen">
<div id="loading-text" class="non-selectable">Loading FCast</div>
<div id="spinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
</div>
<div id="main-container">
<video id="video-player" class="video" autoplay loop>
<source src="../assets/video/background.mp4" type="video/mp4">
@ -42,7 +46,7 @@
</div>
</div>
<div id="window-can-be-closed" class="non-selectable">App will continue to run in the background when app is closed</div>
<div id="window-can-be-closed" class="non-selectable">App will continue to listen for connections when suspended in the background</div>
</div>
</div>
<script src="./renderer.js"></script>

View file

@ -4,10 +4,17 @@
#overlay {
font-family: InterRegular;
font-size: 28px;
}
#title-text {
font-family: OutfitExtraBold;
font-size: 140px;
}
#title-icon {
width: 124px;
height: 124px;
}
#manual-connection-info {
@ -20,4 +27,33 @@
#window-can-be-closed {
font-family: InterRegular;
font-size: 24px;
}
.lds-ring {
width: 120px;
height: 120px;
}
.lds-ring div {
width: 104px;
height: 104px;
}
#loading-screen {
height: 100%;
width: 100%;
object-fit: cover;
background-color: black;
display: flex;
justify-content: center;
align-items: center;
}
#loading-text {
font-size: 100px;
font-weight: 800;
text-align: center;
color: white;
padding-right: 20px;
}

View file

@ -1,10 +1,76 @@
import { onPlay, PlayerControlEvent } from 'common/player/Renderer';
import {
isLive,
onPlay,
player,
PlayerControlEvent,
playerCtrlCaptions,
playerCtrlDuration,
playerCtrlLiveBadge,
playerCtrlPosition,
playerCtrlProgressBar,
playerCtrlProgressBarBuffer,
playerCtrlProgressBarHandle,
playerCtrlProgressBarProgress,
playerCtrlStateUpdate,
playerCtrlVolumeBar,
playerCtrlVolumeBarHandle,
playerCtrlVolumeBarProgress,
videoCaptions,
formatDuration,
} from 'common/player/Renderer';
const captionsBaseHeightCollapsed = 150;
const captionsBaseHeightExpanded = 320;
const captionsLineHeight = 68;
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
let handledCase = false;
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent) {
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;
}
return handledCase;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -18,3 +84,9 @@ export function targetKeyDownEventListener(event: any) {
if (window.webOSAPI.pendingPlay !== null) {
onPlay(null, window.webOSAPI.pendingPlay);
}
export {
captionsBaseHeightCollapsed,
captionsBaseHeightExpanded,
captionsLineHeight,
}

View file

@ -23,10 +23,17 @@
<div id="progressBarInteractiveArea" class="progressBarInteractiveArea" ></div>
</div>
<div class="leftButtonContainer">
<div id="action" class="play"></div>
<div class="positionContainer">
<div id="liveBadge" class="liveBadge" style="display: none">LIVE</div>
<div id="position" class="position">00:00</div>
<!-- <div id="durationSeparator" class="duration">/&nbsp&nbsp</div> -->
<div id="durationSeparator" class="duration"></div>
</div>
<div id="volume" class="volume_high"></div>
<div class="leftButtonContainer">
<div id="action" class="play iconSize"></div>
<div id="volume" class="volume_high iconSize"></div>
<div class="volumeContainer">
<div id="volumeBar" ref="volumeBar" class="volumeBar" ></div>
<div id="volumeBarProgress" class="volumeBarProgress" ></div>
@ -34,17 +41,18 @@
<div id="volumeBarInteractiveArea" class="volumeBarInteractiveArea" ></div>
</div>
<div class="positionContainer">
<!-- <div class="positionContainer">
<div id="liveBadge" class="liveBadge" style="display: none">LIVE</div>
<div id="position" class="position">00:00</div>
<div id="duration" class="duration">/&nbsp&nbsp00:00</div>
</div>
</div> -->
</div>
<div class="buttonContainer">
<!-- <div id="fullscreen" class="fullscreen_on"></div> -->
<div id="speed" class="speed"></div>
<div id="captions" class="captions_off"></div>
<!-- <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="duration" class="duration">00:00</div>
</div>
<div id="speedMenu" class="speedMenu" style="display: none">

View file

@ -1 +1,182 @@
/* WebOS custom player styles */
.container {
height: 240px;
}
.iconSize {
width: 48px;
height: 48px;
background-size: cover;
}
#volume {
display: none;
}
.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;
right: 32px;
height: 8px;
padding-top: 20px;
padding-bottom: 20px;
}
.progressBarInteractiveArea {
height: 8px;
padding-top: 20px;
padding-bottom: 20px;
}
.progressBarChapterContainer {
bottom: 146px;
left: 48px;
right: 48px;
height: 8px;
}
.progressBar {
left: 16px;
width: calc(100% - 32px);
height: 8px;
}
.progressBarBuffer {
left: 16px;
bottom: 8px;
height: 8px;
}
.progressBarProgress {
left: 16px;
bottom: 16px;
height: 8px;
}
.progressBarPosition {
bottom: 50px;
padding: 4px 10px;
font-family: InterRegular;
font-size: 28px;
}
.progressBarHandle {
bottom: 20px;
width: 40px;
height: 40px;
margin-left: -16px;
margin-bottom: -16px;
}
.positionContainer {
position: absolute;
bottom: 48px;
left: 48px;
height: 48px;
font-family: InterRegular;
font-size: 28px;
flex-grow: unset;
}
.position {
font-family: InterRegular;
font-size: 28px;
/* margin-right: 20px; */
}
#duration {
font-family: InterRegular;
font-size: 28px;
}
.liveBadge {
padding: 4px 10px;
border-radius: 8px;
margin-right: 20px;
cursor: pointer;
}
.leftButtonContainer {
bottom: 48px;
left: 48px;
height: 48px;
/* right: 320px; */
right: 32px;
gap: 48px;
justify-content: center;
}
.buttonContainer {
bottom: 48px;
right: 48px;
height: 48px;
gap: 48px;
}
.captionsContainer {
/* display: none; */
/* position: relative; */
/* top: -200px; */
bottom: 320px;
/* margin: auto; */
/* text-align: center; */
/* font-family: InterVariable; */
font-family: InterRegular;
font-size: 56px;
/* font-style: normal; */
/* font-weight: 400; */
/* background-color: rgba(0, 0, 0, 0.5); */
padding: 0px 10px;
/* width: fit-content; */
/* user-select: none; */
/* transition: bottom 0.2s ease-in-out; */
}
#speed {
display: none;
}
#captions {
display: none;
}