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:
parent
41f80880e4
commit
2df64dca89
20 changed files with 457 additions and 92 deletions
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ if (TARGET === 'electron') {
|
|||
playService.cancel();
|
||||
}
|
||||
|
||||
history.pushState({}, '', '../main_window/index.html');
|
||||
window.open('../player/index.html');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ if (TARGET === 'electron') {
|
|||
setSpeedService.cancel();
|
||||
|
||||
// window.open('../main_window/index.html');
|
||||
window.webOS.platformBack();
|
||||
history.back();
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
|
|
|
@ -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 = `/  ${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,
|
||||
};
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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">/  00:00</div>
|
||||
<div id="durationSeparator" class="duration">/  </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">
|
||||
|
|
|
@ -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'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'));
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "node",
|
||||
"target": "ES2015",
|
||||
"module": "ES2015",
|
||||
"moduleResolution": "node10",
|
||||
"sourceMap": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"esModuleInterop": true,
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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">/  </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">/  00: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">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue