1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-08-03 07:47:01 +00:00

webOS: Ported receiver to Electron 2.1.0 changes

This commit is contained in:
Michael Hollister 2025-05-09 10:44:26 -05:00
parent 61c1e9a9c1
commit e3c437a280
16 changed files with 2402 additions and 978 deletions

View file

@ -1,6 +1,6 @@
{
"id": "com.futo.fcast.receiver",
"version": "1.0.1",
"version": "1.1.0",
"vendor": "FUTO",
"type": "web",
"main": "main_window/index.html",

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "com.futo.fcast.receiver",
"version": "1.0.1",
"version": "1.1.0",
"description": "An application implementing a FCast receiver.",
"author": "FUTO",
"license": "MIT",
@ -9,7 +9,7 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"@eslint/js": "^9.10.0",
"@eslint/js": "^9.25.0",
"@types/jest": "^29.5.11",
"@types/mdns": "^0.0.38",
"@types/node-forge": "^1.3.10",
@ -17,17 +17,17 @@
"@types/webostvjs": "^1.2.6",
"@types/workerpool": "^6.1.1",
"@types/ws": "^8.5.10",
"copy-webpack-plugin": "^12.0.2",
"eslint": "^9.10.0",
"globals": "^15.9.0",
"copy-webpack-plugin": "^13.0.0",
"eslint": "^9.25.0",
"globals": "^16.0.0",
"jest": "^29.7.0",
"mdns-js": "github:mdns-js/node-mdns-js",
"ts-jest": "^29.1.1",
"ts-loader": "^9.4.2",
"typescript": "^5.5.4",
"typescript-eslint": "^8.4.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
"webpack": "^5.99.6",
"webpack-cli": "^6.0.1"
},
"dependencies": {
"bufferutil": "^4.0.8",
@ -35,10 +35,9 @@
"hls.js": "^1.5.15",
"http": "^0.0.1-security",
"https": "^1.0.0",
"log4js": "^6.9.1",
"qrcode": "^1.5.3",
"url": "^0.11.3",
"uuid": "^9.0.1",
"ws": "^8.14.2"
"url": "^0.11.4",
"uuid": "^11.0.3",
"ws": "^8.18.0"
}
}

View file

@ -4,6 +4,7 @@ import { preloadData } from 'common/main/Preload';
import { toast, ToastIcon } from 'common/components/Toast';
require('lib/webOSTVjs-1.2.10/webOSTV.js');
require('lib/webOSTVjs-1.2.10/webOSTV-dev.js');
const logger = window.targetAPI.logger;
enum RemoteKeyCode {
Stop = 413,
@ -15,24 +16,57 @@ enum RemoteKeyCode {
}
try {
const toastService = registerService('toast', (message: any) => { toast(message.value.message, message.value.icon, message.value.duration); });
const getDeviceInfoService = registerService('getDeviceInfo', (message: any) => {
console.log(`Main: getDeviceInfo ${JSON.stringify(message)}`);
preloadData.deviceInfo = message.value;
preloadData.onDeviceInfoCb();
}, false);
const onConnectService = registerService('connect', (message: any) => { preloadData.onConnectCb(null, message.value); });
const onDisconnectService = registerService('disconnect', (message: any) => { preloadData.onDisconnectCb(null, message.value); });
const onPingService = registerService('ping', (message: any) => { preloadData.onPingCb(null, message.value); });
const playService = registerService('play', (message: any) => {
let getSessions = null;
const toastService = requestService('toast', (message: any) => { toast(message.value.message, message.value.icon, message.value.duration); });
const getDeviceInfoService = window.webOS.service.request('luna://com.palm.connectionmanager', {
method: 'getStatus',
parameters: {},
onSuccess: (message: any) => {
// logger.info('Network info status message', message);
const deviceName = 'FCast-LGwebOSTV';
const connections = [];
if (message.wired.state !== 'disconnected') {
connections.push({ type: 'wired', name: 'Ethernet', address: message.wired.ipAddress })
}
// wifiDirect never seems to be connected, despite being connected (which is needed for signalLevel...)
// if (message.wifiDirect.state !== 'disconnected') {
if (message.wifi.state !== 'disconnected') {
connections.push({ type: 'wireless', name: message.wifi.ssid, address: message.wifi.ipAddress, signalLevel: 100 })
}
preloadData.deviceInfo = { name: deviceName, interfaces: connections };
preloadData.onDeviceInfoCb();
},
onFailure: (message: any) => {
logger.error(`Main: com.palm.connectionmanager/getStatus ${JSON.stringify(message)}`);
toast(`Main: com.palm.connectionmanager/getStatus ${JSON.stringify(message)}`, ToastIcon.ERROR);
},
// onComplete: (message) => {},
subscribe: true,
resubscribe: true
});
window.targetAPI.getSessions(() => {
return new Promise((resolve, reject) => {
getSessions = requestService('get_sessions', (message: any) => resolve(message.value), (message: any) => reject(message), false);
});
});
const onConnectService = requestService('connect', (message: any) => { preloadData.onConnectCb(null, message.value); });
const onDisconnectService = requestService('disconnect', (message: any) => { preloadData.onDisconnectCb(null, message.value); });
const playService = requestService('play', (message: any) => {
if (message.value !== undefined && message.value.playData !== undefined) {
console.log(`Main: Playing ${JSON.stringify(message)}`);
logger.info(`Main: Playing ${JSON.stringify(message)}`);
sessionStorage.setItem('playData', JSON.stringify(message.value.playData));
getDeviceInfoService.cancel();
getSessions?.cancel();
toastService.cancel();
onConnectService.cancel();
onDisconnectService.cancel();
onPingService.cancel();
playService.cancel();
// WebOS 22 and earlier does not work well using the history API,
@ -44,7 +78,7 @@ try {
const launchHandler = () => {
const params = window.webOSDev.launchParams();
console.log(`Main: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
logger.info(`Main: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
const lastTimestamp = Number(localStorage.getItem('lastTimestamp'));
if (params.playData !== undefined && params.timestamp != lastTimestamp) {
@ -52,9 +86,9 @@ try {
sessionStorage.setItem('playData', JSON.stringify(params.playData));
toastService?.cancel();
getDeviceInfoService?.cancel();
getSessions?.cancel();
onConnectService?.cancel();
onDisconnectService?.cancel();
onPingService?.cancel();
playService?.cancel();
// WebOS 22 and earlier does not work well using the history API,
@ -73,7 +107,7 @@ try {
// };
document.addEventListener('keydown', (event: any) => {
// console.log("KeyDown", event);
// logger.info("KeyDown", event);
switch (event.keyCode) {
// WebOS 22 and earlier does not work well using the history API,
@ -87,11 +121,11 @@ try {
});
}
catch (err) {
console.error(`Main: preload ${JSON.stringify(err)}`);
toast(`Main: preload ${JSON.stringify(err)}`, ToastIcon.ERROR);
logger.error(`Main: preload ${JSON.stringify(err)}`);
toast(`Error starting the application (preload): ${JSON.stringify(err)}`, ToastIcon.ERROR);
}
function registerService(method: string, callback: (message: any) => void, subscribe: boolean = true): any {
function requestService(method: string, successCallback: (message: any) => void, failureCallback?: (message: any) => void, subscribe: boolean = true): any {
const serviceId = 'com.futo.fcast.receiver.service';
return window.webOS.service.request(`luna://${serviceId}/`, {
@ -99,15 +133,18 @@ function registerService(method: string, callback: (message: any) => void, subsc
parameters: {},
onSuccess: (message: any) => {
if (message.value?.subscribed === true) {
console.log(`Main: Registered ${method} handler with service`);
logger.info(`Main: Registered ${method} handler with service`);
}
else {
callback(message);
successCallback(message);
}
},
onFailure: (message: any) => {
console.error(`Main: ${method} ${JSON.stringify(message)}`);
toast(`Main: ${method} ${JSON.stringify(message)}`, ToastIcon.ERROR);
logger.error(`Main: ${method} ${JSON.stringify(message)}`);
if (failureCallback) {
failureCallback(message);
}
},
// onComplete: (message) => {},
subscribe: subscribe,

View file

@ -1,15 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>FCast Receiver</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../assets/fonts/outfit.css" />
<link rel="stylesheet" href="../assets/fonts/inter.css" />
<link rel="stylesheet" href="./common.css" />
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div id="loading-screen">
<div id="loading-text" class="non-selectable">Loading FCast</div>
@ -34,16 +33,34 @@
</div>
</div>
<div id="detail-view" class="card">
<div class="non-selectable card-title">Manual connection information</div>
<div class="non-selectable card-title">Connection Information</div>
<div class="card-title-separator"></div>
<div>
<div id="ips">IPs</div><br />
<div id="ip-ports">Port<br>46899 (TCP), 46898 (WS)</div>
<div id="connection-information-loading">
<div id="connection-information-loading-text" class="lds-ring">Fetching Network Info...</div>
<div id="connection-information-loading-spinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
</div>
<div id="connection-error">
<div id="connection-error-icon"></div>
<div id="connection-error-text">Device not connected to a network</div>
</div>
<div id="connection-information">
<div id="scan-to-connect" class="non-selectable">Scan with a FCast sender app</div>
<canvas id="qr-code"></canvas>
<div id="app-download" class="non-selectable app-download">Need a sender app?<br>Download Grayjay at <a href="https://grayjay.app" target="_blank">https://grayjay.app</a></div>
<br />
<div id="connection-details" class="non-selectable card-title">Connection Details</div>
<div id="connection-details-separator" class="card-title-separator"></div>
<div>
<div id="ips">
<div id="ips-iface-icon"></div>
<div id="ips-iface-text"></div>
<div id="ips-iface-name"></div>
</div><br />
<div id="ip-ports">Port<br>46899 (TCP), 46898 (WS)</div>
</div>
</div>
<div id="automatic-discovery" class="non-selectable">Automatic discovery is available via mDNS</div>
<canvas id="qr-code"></canvas>
<div id="scan-to-connect" class="non-selectable">Scan with a FCast sender app.</div>
<div id="app-download" class="non-selectable app-download">Need a sender app?<br>Download Grayjay at <u class="app-download">https://grayjay.app</u></div>
</div>
</div>
@ -57,5 +74,4 @@
<script src="./preload.js"></script>
<script src="./renderer.js"></script>
</body>
</html>

View file

@ -4,15 +4,11 @@
#overlay {
font-family: InterRegular;
font-size: 28px;
/* gap not supported in WebOS 6.0 */
gap: unset;
}
#main-view {
padding: 25px;
/* gap not supported in WebOS 6.0 */
gap: unset;
margin-right: 15vw;
@ -20,16 +16,6 @@
#title-text {
font-family: OutfitExtraBold;
font-size: 140px;
}
#title-icon {
width: 124px;
height: 124px;
}
#manual-connection-info {
font-family: InterBold;
}
#scan-to-connect {
@ -43,16 +29,6 @@
#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 {
@ -74,23 +50,10 @@
padding-right: 20px;
}
#connection-check {
width: 104px;
height: 104px;
}
#toast-notification {
gap: unset;
top: -250px;
}
#toast-icon {
width: 88px;
height: 88px;
margin-right: 20px;
}
#toast-text {
font-family: InterRegular;
font-size: 28px;
}

View file

@ -5,9 +5,12 @@ import { PlaybackErrorMessage, PlaybackUpdateMessage, VolumeUpdateMessage } from
import { toast, ToastIcon } from 'common/components/Toast';
require('lib/webOSTVjs-1.2.10/webOSTV.js');
require('lib/webOSTVjs-1.2.10/webOSTV-dev.js');
const logger = window.targetAPI.logger;
try {
const serviceId = 'com.futo.fcast.receiver.service';
let getSessions = null;
window.webOSAPI = {
pendingPlay: JSON.parse(sessionStorage.getItem('playData'))
};
@ -18,7 +21,7 @@ try {
parameters: { error },
onSuccess: () => {},
onFailure: (message: any) => {
console.error(`Player: send_playback_error ${JSON.stringify(message)}`);
logger.error(`Player: send_playback_error ${JSON.stringify(message)}`);
},
});
};
@ -27,11 +30,11 @@ try {
method: 'send_playback_update',
parameters: { update },
// onSuccess: (message: any) => {
// console.log(`Player: send_playback_update ${JSON.stringify(message)}`);
// logger.info(`Player: send_playback_update ${JSON.stringify(message)}`);
// },
onSuccess: () => {},
onFailure: (message: any) => {
console.error(`Player: send_playback_update ${JSON.stringify(message)}`);
logger.error(`Player: send_playback_update ${JSON.stringify(message)}`);
},
});
};
@ -41,7 +44,7 @@ try {
parameters: { update },
onSuccess: () => {},
onFailure: (message: any) => {
console.error(`Player: send_volume_update ${JSON.stringify(message)}`);
logger.error(`Player: send_volume_update ${JSON.stringify(message)}`);
},
});
};
@ -50,9 +53,9 @@ try {
method:"play",
parameters: {},
onSuccess: (message: any) => {
// console.log(JSON.stringify(message));
// logger.info(JSON.stringify(message));
if (message.value.subscribed === true) {
console.log('Player: Registered play handler with service');
logger.info('Player: Registered play handler with service');
}
if (message.value.playData !== null) {
@ -65,15 +68,15 @@ try {
}
},
onFailure: (message: any) => {
console.error(`Player: play ${JSON.stringify(message)}`);
logger.error(`Player: play ${JSON.stringify(message)}`);
},
subscribe: true,
resubscribe: true
});
const pauseService = registerService('pause', () => { preloadData.onPauseCb(); });
const resumeService = registerService('resume', () => { preloadData.onResumeCb(); });
const stopService = registerService('stop', () => {
const pauseService = requestService('pause', () => { preloadData.onPauseCb(); });
const resumeService = requestService('resume', () => { preloadData.onResumeCb(); });
const stopService = requestService('stop', () => {
playService.cancel();
pauseService.cancel();
resumeService.cancel();
@ -81,6 +84,9 @@ try {
seekService.cancel();
setVolumeService.cancel();
setSpeedService.cancel();
getSessions?.cancel();
onConnectService.cancel();
onDisconnectService.cancel();
// WebOS 22 and earlier does not work well using the history API,
// so manually handling page navigation...
@ -88,14 +94,23 @@ try {
window.open('../main_window/index.html', '_self');
});
const seekService = registerService('seek', (message: any) => { preloadData.onSeekCb(null, message.value); });
const setVolumeService = registerService('setvolume', (message: any) => { preloadData.onSetVolumeCb(null, message.value); });
const setSpeedService = registerService('setspeed', (message: any) => { preloadData.onSetSpeedCb(null, message.value); });
const seekService = requestService('seek', (message: any) => { preloadData.onSeekCb(null, message.value); });
const setVolumeService = requestService('setvolume', (message: any) => { preloadData.onSetVolumeCb(null, message.value); });
const setSpeedService = requestService('setspeed', (message: any) => { preloadData.onSetSpeedCb(null, message.value); });
window.targetAPI.getSessions(() => {
return new Promise((resolve, reject) => {
getSessions = requestService('get_sessions', (message: any) => resolve(message.value), (message: any) => reject(message), false);
});
});
const onConnectService = requestService('connect', (message: any) => { preloadData.onConnectCb(null, message.value); });
const onDisconnectService = requestService('disconnect', (message: any) => { preloadData.onDisconnectCb(null, message.value); });
const launchHandler = () => {
// args don't seem to be passed in via event despite what documentation says...
const params = window.webOSDev.launchParams();
console.log(`Player: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
logger.info(`Player: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
const lastTimestamp = Number(localStorage.getItem('lastTimestamp'));
if (params.playData !== undefined && params.timestamp != lastTimestamp) {
@ -108,6 +123,9 @@ try {
seekService?.cancel();
setVolumeService?.cancel();
setSpeedService?.cancel();
getSessions?.cancel();
onConnectService?.cancel();
onDisconnectService?.cancel();
// WebOS 22 and earlier does not work well using the history API,
// so manually handling page navigation...
@ -121,27 +139,30 @@ try {
}
catch (err) {
console.error(`Player: preload ${JSON.stringify(err)}`);
toast(`Player: preload ${JSON.stringify(err)}`, ToastIcon.ERROR);
logger.error(`Player: preload ${JSON.stringify(err)}`);
toast(`Error starting the video player (preload): ${JSON.stringify(err)}`, ToastIcon.ERROR);
}
function registerService(method: string, callback: (message: any) => void, subscribe: boolean = true): any {
function requestService(method: string, successCallback: (message: any) => void, failureCallback?: (message: any) => void, subscribe: boolean = true): any {
const serviceId = 'com.futo.fcast.receiver.service';
return window.webOS.service.request(`luna://${serviceId}/`, {
method: method,
parameters: {},
onSuccess: (message: any) => {
if (message.value.subscribed === true) {
console.log(`Player: Registered ${method} handler with service`);
if (message.value?.subscribed === true) {
logger.info(`Player: Registered ${method} handler with service`);
}
else {
callback(message);
successCallback(message);
}
},
onFailure: (message: any) => {
console.error(`Player: ${method} ${JSON.stringify(message)}`);
// toast(`Player: ${method} ${JSON.stringify(message)}`, ToastIcon.ERROR);
logger.error(`Main: ${method} ${JSON.stringify(message)}`);
if (failureCallback) {
failureCallback(message);
}
},
// onComplete: (message) => {},
subscribe: subscribe,

View file

@ -3,6 +3,7 @@
<head>
<title>FCast Receiver</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../assets/fonts/inter.css" />
<link rel="stylesheet" href="./common.css" />
<link rel="stylesheet" href="./style.css" />
@ -93,6 +94,11 @@
</div>
</div>
<div id="toast-notification">
<div id="toast-icon"></div>
<div id="toast-text"></div>
</div>
<script src="./renderer.js"></script>
</body>
</html>