mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-08-03 07:47:01 +00:00
webOS: Initial port of Electron v2.2.0 changes
This commit is contained in:
parent
b08c3dab95
commit
4c1cb6cf8e
28 changed files with 1273 additions and 532 deletions
1
receivers/webos/fcast-receiver/.npmrc
Normal file
1
receivers/webos/fcast-receiver/.npmrc
Normal file
|
@ -0,0 +1 @@
|
|||
@futo:registry=https://gitlab.futo.org/api/v4/projects/305/packages/npm/
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "com.futo.fcast.receiver",
|
||||
"version": "1.1.0",
|
||||
"version": "2.0.0",
|
||||
"vendor": "FUTO",
|
||||
"type": "web",
|
||||
"main": "main_window/index.html",
|
||||
|
|
98
receivers/webos/fcast-receiver/lib/common.ts
Normal file
98
receivers/webos/fcast-receiver/lib/common.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
const logger = window.targetAPI.logger;
|
||||
const serviceId = 'com.futo.fcast.receiver.service';
|
||||
|
||||
export enum RemoteKeyCode {
|
||||
Stop = 413,
|
||||
Rewind = 412,
|
||||
Play = 415,
|
||||
Pause = 19,
|
||||
FastForward = 417,
|
||||
Back = 461,
|
||||
}
|
||||
|
||||
export function requestService(method: string, successCb: (message: any) => void, failureCb?: (message: any) => void, onCompleteCb?: (message: any) => void): any {
|
||||
return window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: method,
|
||||
parameters: {},
|
||||
onSuccess: (message: any) => {
|
||||
if (message.value?.subscribed === true) {
|
||||
logger.info(`requestService: Registered ${method} handler with service`);
|
||||
}
|
||||
else {
|
||||
successCb(message);
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`requestService: ${method} ${JSON.stringify(message)}`);
|
||||
|
||||
if (failureCb) {
|
||||
failureCb(message);
|
||||
}
|
||||
},
|
||||
onComplete: (message: any) => {
|
||||
if (onCompleteCb) {
|
||||
onCompleteCb(message);
|
||||
}
|
||||
},
|
||||
subscribe: true,
|
||||
resubscribe: true
|
||||
});
|
||||
}
|
||||
|
||||
export function callService(method: string, parameters?: any, successCb?: (message: any) => void, failureCb?: (message: any) => void, onCompleteCb?: (message: any) => void) {
|
||||
return window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: method,
|
||||
parameters: parameters,
|
||||
onSuccess: (message: any) => {
|
||||
if (successCb) {
|
||||
successCb(message);
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`callService: ${method} ${JSON.stringify(message)}`);
|
||||
|
||||
if (failureCb) {
|
||||
failureCb(message);
|
||||
}
|
||||
},
|
||||
onComplete: (message: any) => {
|
||||
if (onCompleteCb) {
|
||||
onCompleteCb(message);
|
||||
}
|
||||
},
|
||||
subscribe: false,
|
||||
resubscribe: false
|
||||
});
|
||||
}
|
||||
|
||||
export function targetKeyUpEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
|
||||
let handledCase = false;
|
||||
let key = '';
|
||||
|
||||
// .keyCode instead of alternatives is required to work properly on webOS
|
||||
switch (event.keyCode) {
|
||||
// Unhandled cases (used for replacing undefined key codes)
|
||||
case RemoteKeyCode.Stop:
|
||||
key = 'Stop';
|
||||
break;
|
||||
case RemoteKeyCode.Rewind:
|
||||
key = 'Rewind';
|
||||
break;
|
||||
case RemoteKeyCode.Play:
|
||||
key = 'Play';
|
||||
break;
|
||||
case RemoteKeyCode.Pause:
|
||||
key = 'Pause';
|
||||
break;
|
||||
case RemoteKeyCode.FastForward:
|
||||
key = 'FastForward';
|
||||
break;
|
||||
case RemoteKeyCode.Back:
|
||||
key = 'Back';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return { handledCase: handledCase, key: key };
|
||||
};
|
211
receivers/webos/fcast-receiver/package-lock.json
generated
211
receivers/webos/fcast-receiver/package-lock.json
generated
|
@ -1,26 +1,26 @@
|
|||
{
|
||||
"name": "com.futo.fcast.receiver",
|
||||
"version": "1.1.0",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "com.futo.fcast.receiver",
|
||||
"version": "1.1.0",
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bufferutil": "^4.0.8",
|
||||
"dashjs": "^4.7.4",
|
||||
"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.4",
|
||||
"uuid": "^11.0.3",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@futo/mdns-js": "1.0.3",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/mdns": "^0.0.38",
|
||||
"@types/node-forge": "^1.3.10",
|
||||
|
@ -32,13 +32,15 @@
|
|||
"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.99.6",
|
||||
"webpack-cli": "^6.0.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"utf-8-validate": "^6.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
|
@ -837,6 +839,47 @@
|
|||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@futo/mdns-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://gitlab.futo.org/api/v4/projects/305/packages/npm/@futo/mdns-js/-/@futo/mdns-js-1.0.3.tgz",
|
||||
"integrity": "sha1-y25rzWUSYkRu0bhkR472LJLMdcc=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "~3.1.0",
|
||||
"dns-js": "~0.2.1",
|
||||
"semver": "~5.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@futo/mdns-js/node_modules/debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@futo/mdns-js/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@futo/mdns-js/node_modules/semver": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
||||
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
|
@ -3045,11 +3088,19 @@
|
|||
"integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/date-format": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz",
|
||||
"integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
|
@ -3880,7 +3931,6 @@
|
|||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
|
||||
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
|
@ -3903,6 +3953,20 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6 <7 || >=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
|
@ -4071,7 +4135,6 @@
|
|||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/graphemer": {
|
||||
|
@ -4134,11 +4197,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/http": {
|
||||
"version": "0.0.1-security",
|
||||
"resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz",
|
||||
"integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g=="
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
|
@ -4156,12 +4214,6 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/https": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
|
||||
"integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||
|
@ -5261,6 +5313,15 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
@ -5378,6 +5439,22 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/log4js": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz",
|
||||
"integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"date-format": "^4.0.14",
|
||||
"debug": "^4.3.4",
|
||||
"flatted": "^3.2.7",
|
||||
"rfdc": "^1.3.0",
|
||||
"streamroller": "^3.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
|
@ -5430,47 +5507,6 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdns-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "git+ssh://git@github.com/mdns-js/node-mdns-js.git#4fb9220ec8852bae9e2781917f649821b9df539d",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"debug": "~3.1.0",
|
||||
"dns-js": "~0.2.1",
|
||||
"semver": "~5.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mdns-js/node_modules/debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mdns-js/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mdns-js/node_modules/semver": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
||||
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||
|
@ -5575,7 +5611,6 @@
|
|||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
|
@ -6425,6 +6460,12 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rfdc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||
|
@ -6840,6 +6881,20 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/streamroller": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz",
|
||||
"integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"date-format": "^4.0.14",
|
||||
"debug": "^4.3.4",
|
||||
"fs-extra": "^8.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-length": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
|
||||
|
@ -7358,6 +7413,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
|
@ -7428,17 +7492,30 @@
|
|||
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/utf-8-validate": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.5.tgz",
|
||||
"integrity": "sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-to-istanbul": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "com.futo.fcast.receiver",
|
||||
"version": "1.1.0",
|
||||
"version": "2.0.0",
|
||||
"description": "An application implementing a FCast receiver.",
|
||||
"author": "FUTO",
|
||||
"license": "MIT",
|
||||
|
@ -10,6 +10,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@futo/mdns-js": "1.0.3",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/mdns": "^0.0.38",
|
||||
"@types/node-forge": "^1.3.10",
|
||||
|
@ -21,7 +22,6 @@
|
|||
"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",
|
||||
|
@ -33,11 +33,13 @@
|
|||
"bufferutil": "^4.0.8",
|
||||
"dashjs": "^4.7.4",
|
||||
"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.4",
|
||||
"uuid": "^11.0.3",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"utf-8-validate": "^6.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,153 +1,139 @@
|
|||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { preloadData } from 'common/main/Preload';
|
||||
import { toast, ToastIcon } from 'common/components/Toast';
|
||||
import { ToastIcon } from 'common/components/Toast';
|
||||
import { EventMessage } from 'common/Packets';
|
||||
import { callService, requestService } from 'lib/common';
|
||||
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,
|
||||
Rewind = 412,
|
||||
Play = 415,
|
||||
Pause = 19,
|
||||
FastForward = 417,
|
||||
Back = 461,
|
||||
}
|
||||
|
||||
try {
|
||||
let getSessions = null;
|
||||
const serviceId = 'com.futo.fcast.receiver.service';
|
||||
let getSessionsService = null;
|
||||
let networkChangedService = null;
|
||||
let visibilityChangedService = 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: {},
|
||||
const toastService = requestService('toast', (message: any) => { preloadData.onToastCb(message.value.message, message.value.icon, message.value.duration); });
|
||||
const getDeviceInfoService = window.webOSDev.connection.getStatus({
|
||||
onSuccess: (message: any) => {
|
||||
// logger.info('Network info status message', message);
|
||||
logger.info('Network info status message', message);
|
||||
const deviceName = 'FCast-LGwebOSTV';
|
||||
const connections = [];
|
||||
let fallback = true;
|
||||
|
||||
if (message.wired.state !== 'disconnected') {
|
||||
connections.push({ type: 'wired', name: 'Ethernet', address: message.wired.ipAddress })
|
||||
connections.push({ type: 'wired', name: 'Ethernet', address: message.wired.ipAddress });
|
||||
fallback = false;
|
||||
}
|
||||
|
||||
// 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 })
|
||||
connections.push({ type: 'wireless', name: message.wifi.ssid, address: message.wifi.ipAddress, signalLevel: 100 });
|
||||
fallback = false;
|
||||
}
|
||||
|
||||
preloadData.deviceInfo = { name: deviceName, interfaces: connections };
|
||||
preloadData.onDeviceInfoCb();
|
||||
if (fallback) {
|
||||
networkChangedService = callService('network_changed', { fallback: fallback }, (message: any) => {
|
||||
logger.info('Fallback network interfaces', message);
|
||||
for (const ipAddr of message.value) {
|
||||
connections.push({ type: 'wired', name: 'Ethernet', address: ipAddr });
|
||||
}
|
||||
|
||||
preloadData.deviceInfo = { name: deviceName, interfaces: connections };
|
||||
preloadData.onDeviceInfoCb();
|
||||
}, (message: any) => {
|
||||
logger.error('Main: preload - error fetching network interfaces', message);
|
||||
preloadData.onToastCb('Error detecting network interfaces', ToastIcon.ERROR);
|
||||
}, () => {
|
||||
networkChangedService = null;
|
||||
});
|
||||
}
|
||||
else {
|
||||
networkChangedService = callService('network_changed', { fallback: fallback }, null, null, () => {
|
||||
networkChangedService = null;
|
||||
});
|
||||
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);
|
||||
|
||||
preloadData.onToastCb(`Main: com.palm.connectionmanager/getStatus ${JSON.stringify(message)}`, ToastIcon.ERROR);
|
||||
},
|
||||
// onComplete: (message) => {},
|
||||
subscribe: true,
|
||||
resubscribe: true
|
||||
});
|
||||
|
||||
const onEventSubscribedKeysUpdateService = requestService('event_subscribed_keys_update', (message: any) => { preloadData.onEventSubscribedKeysUpdate(message.value); });
|
||||
window.targetAPI.getSessions(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
getSessions = requestService('get_sessions', (message: any) => resolve(message.value), (message: any) => reject(message), false);
|
||||
getSessionsService = callService('get_sessions', {}, (message: any) => resolve(message.value), (message: any) => reject(message));
|
||||
});
|
||||
});
|
||||
|
||||
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) {
|
||||
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();
|
||||
playService.cancel();
|
||||
preloadData.sendEventCb = (event: EventMessage) => {
|
||||
window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: 'send_event',
|
||||
parameters: { event },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => { logger.error(`Player: send_event ${JSON.stringify(message)}`); },
|
||||
});
|
||||
};
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
// so manually handling page navigation...
|
||||
// history.pushState({}, '', '../main_window/index.html');
|
||||
window.open('../player/index.html', '_self');
|
||||
}
|
||||
const playService = requestService('play', (message: any) => {
|
||||
logger.info(`Main: Playing ${JSON.stringify(message)}`);
|
||||
play(message.value);
|
||||
});
|
||||
|
||||
const launchHandler = () => {
|
||||
const params = window.webOSDev.launchParams();
|
||||
logger.info(`Main: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
|
||||
|
||||
// WebOS 6.0 and earlier: Timestamp tracking seems to be necessary as launch event is raised regardless if app is in foreground or not
|
||||
const lastTimestamp = Number(localStorage.getItem('lastTimestamp'));
|
||||
if (params.playData !== undefined && params.timestamp != lastTimestamp) {
|
||||
if (params.messageInfo !== undefined && params.timestamp != lastTimestamp) {
|
||||
localStorage.setItem('lastTimestamp', params.timestamp);
|
||||
sessionStorage.setItem('playData', JSON.stringify(params.playData));
|
||||
toastService?.cancel();
|
||||
getDeviceInfoService?.cancel();
|
||||
getSessions?.cancel();
|
||||
onConnectService?.cancel();
|
||||
onDisconnectService?.cancel();
|
||||
playService?.cancel();
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
// so manually handling page navigation...
|
||||
// history.pushState({}, '', '../main_window/index.html');
|
||||
window.open('../player/index.html', '_self');
|
||||
play(params.messageInfo);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('webOSLaunch', launchHandler);
|
||||
document.addEventListener('webOSRelaunch', launchHandler);
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
visibilityChangedService = callService('visibility_changed', { hidden: document.hidden, window: 'main' }, null, null, () => {
|
||||
visibilityChangedService = null;
|
||||
})
|
||||
});
|
||||
|
||||
// Cannot go back to a state where user was previously casting a video, so exit.
|
||||
// window.onpopstate = () => {
|
||||
// window.webOS.platformBack();
|
||||
// };
|
||||
|
||||
document.addEventListener('keydown', (event: any) => {
|
||||
// logger.info("KeyDown", event);
|
||||
const play = (messageInfo: any) => {
|
||||
sessionStorage.setItem('playInfo', JSON.stringify(messageInfo));
|
||||
|
||||
switch (event.keyCode) {
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
// so manually handling page navigation...
|
||||
case RemoteKeyCode.Back:
|
||||
window.webOS.platformBack();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
getDeviceInfoService?.cancel();
|
||||
onEventSubscribedKeysUpdateService?.cancel();
|
||||
getSessionsService?.cancel();
|
||||
toastService?.cancel();
|
||||
onConnectService?.cancel();
|
||||
onDisconnectService?.cancel();
|
||||
playService?.cancel();
|
||||
networkChangedService?.cancel();
|
||||
visibilityChangedService?.cancel();
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
// so manually handling page navigation...
|
||||
// history.pushState({}, '', '../main_window/index.html');
|
||||
window.open(`../${messageInfo.contentViewer}/index.html`, '_self');
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
logger.error(`Main: preload ${JSON.stringify(err)}`);
|
||||
toast(`Error starting the application (preload): ${JSON.stringify(err)}`, ToastIcon.ERROR);
|
||||
}
|
||||
|
||||
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) {
|
||||
logger.info(`Main: Registered ${method} handler with service`);
|
||||
}
|
||||
else {
|
||||
successCallback(message);
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`Main: ${method} ${JSON.stringify(message)}`);
|
||||
|
||||
if (failureCallback) {
|
||||
failureCallback(message);
|
||||
}
|
||||
},
|
||||
// onComplete: (message) => {},
|
||||
subscribe: subscribe,
|
||||
resubscribe: subscribe
|
||||
});
|
||||
preloadData.onToastCb(`Error starting the application: ${JSON.stringify(err)}`, ToastIcon.ERROR);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import 'common/main/Renderer';
|
||||
import { RemoteKeyCode } from 'lib/common';
|
||||
import * as common from 'lib/common';
|
||||
|
||||
const backgroundVideo = document.getElementById('video-player');
|
||||
const loadingScreen = document.getElementById('loading-screen');
|
||||
|
@ -30,3 +32,45 @@ backgroundVideo.onplaying = () => {
|
|||
export function onQRCodeRendered() {
|
||||
qrCodeRendered = true;
|
||||
}
|
||||
|
||||
export function targetKeyDownEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
|
||||
let handledCase = false;
|
||||
let key = '';
|
||||
|
||||
switch (event.keyCode) {
|
||||
// Unhandled cases (used for replacing undefined key codes)
|
||||
case RemoteKeyCode.Stop:
|
||||
key = 'Stop';
|
||||
break;
|
||||
case RemoteKeyCode.Rewind:
|
||||
key = 'Rewind';
|
||||
break;
|
||||
case RemoteKeyCode.Play:
|
||||
key = 'Play';
|
||||
break;
|
||||
case RemoteKeyCode.Pause:
|
||||
key = 'Pause';
|
||||
break;
|
||||
case RemoteKeyCode.FastForward:
|
||||
key = 'FastForward';
|
||||
break;
|
||||
|
||||
// Handled cases
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
// so manually handling page navigation...
|
||||
case RemoteKeyCode.Back:
|
||||
window.webOS.platformBack();
|
||||
handledCase = true;
|
||||
key = 'Back';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return { handledCase: handledCase, key: key };
|
||||
};
|
||||
|
||||
export function targetKeyUpEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
|
||||
return common.targetKeyUpEventListener(event);
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<div id="spinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
<div id="main-container">
|
||||
<img id="image-background"/>
|
||||
<video id="video-player" class="video" autoplay loop>
|
||||
<source src="../assets/video/background.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { preloadData } from 'common/player/Preload';
|
||||
import { PlaybackErrorMessage, PlaybackUpdateMessage, VolumeUpdateMessage } from 'common/Packets';
|
||||
import { EventMessage, PlaybackErrorMessage, PlaybackUpdateMessage, PlayMessage, VolumeUpdateMessage } from 'common/Packets';
|
||||
import { callService, requestService } from 'lib/common';
|
||||
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;
|
||||
const serviceId = 'com.futo.fcast.receiver.service';
|
||||
|
||||
try {
|
||||
const serviceId = 'com.futo.fcast.receiver.service';
|
||||
let getSessions = null;
|
||||
|
||||
window.webOSAPI = {
|
||||
pendingPlay: JSON.parse(sessionStorage.getItem('playData'))
|
||||
pendingPlay: JSON.parse(sessionStorage.getItem('playInfo'))
|
||||
};
|
||||
const contentViewer = window.webOSAPI.pendingPlay?.contentViewer;
|
||||
|
||||
preloadData.sendPlaybackErrorCb = (error: PlaybackErrorMessage) => {
|
||||
window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
|
@ -48,45 +50,73 @@ try {
|
|||
},
|
||||
});
|
||||
};
|
||||
preloadData.sendEventCb = (event: EventMessage) => {
|
||||
window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: 'send_event',
|
||||
parameters: { event },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => { logger.error(`Player: send_event ${JSON.stringify(message)}`); },
|
||||
});
|
||||
};
|
||||
|
||||
const playService = window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method:"play",
|
||||
parameters: {},
|
||||
onSuccess: (message: any) => {
|
||||
// logger.info(JSON.stringify(message));
|
||||
if (message.value.subscribed === true) {
|
||||
logger.info('Player: Registered play handler with service');
|
||||
}
|
||||
const playService = requestService('play', (message: any) => {
|
||||
if (contentViewer !== message.value.contentViewer) {
|
||||
playService?.cancel();
|
||||
pauseService?.cancel();
|
||||
resumeService?.cancel();
|
||||
stopService?.cancel();
|
||||
seekService?.cancel();
|
||||
setVolumeService?.cancel();
|
||||
setSpeedService?.cancel();
|
||||
onSetPlaylistItemService?.cancel();
|
||||
getSessions?.cancel();
|
||||
onEventSubscribedKeysUpdateService?.cancel();
|
||||
onConnectService?.cancel();
|
||||
onDisconnectService?.cancel();
|
||||
onPlayPlaylistService?.cancel();
|
||||
|
||||
if (message.value.playData !== null) {
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
// so manually handling page navigation...
|
||||
// history.pushState({}, '', '../main_window/index.html');
|
||||
window.open(`../${message.value.contentViewer}/index.html`, '_self');
|
||||
}
|
||||
else {
|
||||
if (message.value.rendererEvent === 'play-playlist') {
|
||||
if (preloadData.onPlayCb === undefined) {
|
||||
window.webOSAPI.pendingPlay = message.value.playData;
|
||||
window.webOSAPI.pendingPlay = message.value;
|
||||
}
|
||||
else {
|
||||
preloadData.onPlayCb(null, message.value.playData);
|
||||
preloadData.onPlayPlaylistCb(null, message.value.rendererMessage);
|
||||
}
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`Player: play ${JSON.stringify(message)}`);
|
||||
},
|
||||
subscribe: true,
|
||||
resubscribe: true
|
||||
else {
|
||||
if (preloadData.onPlayCb === undefined) {
|
||||
window.webOSAPI.pendingPlay = message.value;
|
||||
}
|
||||
else {
|
||||
preloadData.onPlayCb(null, message.value.rendererMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, (message: any) => {
|
||||
logger.error(`Player: play ${JSON.stringify(message)}`);
|
||||
});
|
||||
|
||||
const pauseService = requestService('pause', () => { preloadData.onPauseCb(); });
|
||||
const resumeService = requestService('resume', () => { preloadData.onResumeCb(); });
|
||||
const stopService = requestService('stop', () => {
|
||||
playService.cancel();
|
||||
pauseService.cancel();
|
||||
resumeService.cancel();
|
||||
stopService.cancel();
|
||||
seekService.cancel();
|
||||
setVolumeService.cancel();
|
||||
setSpeedService.cancel();
|
||||
playService?.cancel();
|
||||
pauseService?.cancel();
|
||||
resumeService?.cancel();
|
||||
stopService?.cancel();
|
||||
seekService?.cancel();
|
||||
setVolumeService?.cancel();
|
||||
setSpeedService?.cancel();
|
||||
onSetPlaylistItemService?.cancel();
|
||||
getSessions?.cancel();
|
||||
onConnectService.cancel();
|
||||
onDisconnectService.cancel();
|
||||
onEventSubscribedKeysUpdateService?.cancel();
|
||||
onConnectService?.cancel();
|
||||
onDisconnectService?.cancel();
|
||||
onPlayPlaylistService?.cancel();
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
// so manually handling page navigation...
|
||||
|
@ -97,25 +127,38 @@ try {
|
|||
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); });
|
||||
const onSetPlaylistItemService = requestService('setplaylistitem', (message: any) => { preloadData.onSetPlaylistItemCb(null, message.value); });
|
||||
|
||||
preloadData.sendPlayRequestCb = (message: PlayMessage, playlistIndex: number) => {
|
||||
window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: 'play_request',
|
||||
parameters: { message: message, playlistIndex: playlistIndex },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => { logger.error(`Player: play_request ${playlistIndex} ${JSON.stringify(message)}`); },
|
||||
});
|
||||
};
|
||||
window.targetAPI.getSessions(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
getSessions = requestService('get_sessions', (message: any) => resolve(message.value), (message: any) => reject(message), false);
|
||||
getSessions = callService('get_sessions', {}, (message: any) => resolve(message.value), (message: any) => reject(message));
|
||||
});
|
||||
});
|
||||
|
||||
const onEventSubscribedKeysUpdateService = requestService('event_subscribed_keys_update', (message: any) => { preloadData.onEventSubscribedKeysUpdate(message.value); });
|
||||
const onConnectService = requestService('connect', (message: any) => { preloadData.onConnectCb(null, message.value); });
|
||||
const onDisconnectService = requestService('disconnect', (message: any) => { preloadData.onDisconnectCb(null, message.value); });
|
||||
const onPlayPlaylistService = requestService('play-playlist', (message: any) => { preloadData.onPlayPlaylistCb(null, message.value); });
|
||||
|
||||
const launchHandler = () => {
|
||||
// args don't seem to be passed in via event despite what documentation says...
|
||||
const params = window.webOSDev.launchParams();
|
||||
logger.info(`Player: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
|
||||
|
||||
// WebOS 6.0 and earlier: Timestamp tracking seems to be necessary as launch event is raised regardless if app is in foreground or not
|
||||
const lastTimestamp = Number(localStorage.getItem('lastTimestamp'));
|
||||
if (params.playData !== undefined && params.timestamp != lastTimestamp) {
|
||||
if (params.messageInfo !== undefined && params.timestamp != lastTimestamp) {
|
||||
localStorage.setItem('lastTimestamp', params.timestamp);
|
||||
sessionStorage.setItem('playData', JSON.stringify(params.playData));
|
||||
sessionStorage.setItem('playInfo', JSON.stringify(params.messageInfo));
|
||||
|
||||
playService?.cancel();
|
||||
pauseService?.cancel();
|
||||
resumeService?.cancel();
|
||||
|
@ -123,49 +166,26 @@ try {
|
|||
seekService?.cancel();
|
||||
setVolumeService?.cancel();
|
||||
setSpeedService?.cancel();
|
||||
onSetPlaylistItemService?.cancel();
|
||||
getSessions?.cancel();
|
||||
onEventSubscribedKeysUpdateService?.cancel();
|
||||
onConnectService?.cancel();
|
||||
onDisconnectService?.cancel();
|
||||
onPlayPlaylistService?.cancel();
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
// so manually handling page navigation...
|
||||
// history.pushState({}, '', '../main_window/index.html');
|
||||
window.open('../player/index.html', '_self');
|
||||
window.open(`../${params.messageInfo.contentViewer}/index.html`, '_self');
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('webOSLaunch', launchHandler);
|
||||
document.addEventListener('webOSRelaunch', launchHandler);
|
||||
document.addEventListener('visibilitychange', () => callService('visibility_changed', { hidden: document.hidden, window: contentViewer }));
|
||||
|
||||
}
|
||||
catch (err) {
|
||||
logger.error(`Player: preload ${JSON.stringify(err)}`);
|
||||
toast(`Error starting the video player (preload): ${JSON.stringify(err)}`, ToastIcon.ERROR);
|
||||
}
|
||||
|
||||
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) {
|
||||
logger.info(`Player: Registered ${method} handler with service`);
|
||||
}
|
||||
else {
|
||||
successCallback(message);
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`Main: ${method} ${JSON.stringify(message)}`);
|
||||
|
||||
if (failureCallback) {
|
||||
failureCallback(message);
|
||||
}
|
||||
},
|
||||
// onComplete: (message) => {},
|
||||
subscribe: subscribe,
|
||||
resubscribe: subscribe
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
isLive,
|
||||
onPlay,
|
||||
onPlayPlaylist,
|
||||
player,
|
||||
PlayerControlEvent,
|
||||
playerCtrlCaptions,
|
||||
|
@ -20,20 +21,13 @@ import {
|
|||
skipBack,
|
||||
skipForward,
|
||||
} from 'common/player/Renderer';
|
||||
import { RemoteKeyCode } from 'lib/common';
|
||||
import * as common from 'lib/common';
|
||||
|
||||
const captionsBaseHeightCollapsed = 150;
|
||||
const captionsBaseHeightExpanded = 320;
|
||||
const captionsLineHeight = 68;
|
||||
|
||||
enum RemoteKeyCode {
|
||||
Stop = 413,
|
||||
Rewind = 412,
|
||||
Play = 415,
|
||||
Pause = 19,
|
||||
FastForward = 417,
|
||||
Back = 461,
|
||||
}
|
||||
|
||||
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
|
||||
let handledCase = false;
|
||||
|
||||
|
@ -84,21 +78,23 @@ export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean
|
|||
return handledCase;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function targetKeyDownEventListener(event: any): boolean {
|
||||
export function targetKeyDownEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
|
||||
let handledCase = false;
|
||||
let key = '';
|
||||
|
||||
switch (event.keyCode) {
|
||||
case RemoteKeyCode.Stop:
|
||||
// history.back();
|
||||
window.open('../main_window/index.html', '_self');
|
||||
handledCase = true;
|
||||
key = 'Stop';
|
||||
break;
|
||||
|
||||
case RemoteKeyCode.Rewind:
|
||||
skipBack();
|
||||
event.preventDefault();
|
||||
handledCase = true;
|
||||
key = 'Rewind';
|
||||
break;
|
||||
|
||||
case RemoteKeyCode.Play:
|
||||
|
@ -107,6 +103,7 @@ export function targetKeyDownEventListener(event: any): boolean {
|
|||
}
|
||||
event.preventDefault();
|
||||
handledCase = true;
|
||||
key = 'Play';
|
||||
break;
|
||||
case RemoteKeyCode.Pause:
|
||||
if (!player.isPaused()) {
|
||||
|
@ -114,12 +111,14 @@ export function targetKeyDownEventListener(event: any): boolean {
|
|||
}
|
||||
event.preventDefault();
|
||||
handledCase = true;
|
||||
key = 'Pause';
|
||||
break;
|
||||
|
||||
case RemoteKeyCode.FastForward:
|
||||
skipForward();
|
||||
event.preventDefault();
|
||||
handledCase = true;
|
||||
key = 'FastForward';
|
||||
break;
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
|
@ -129,17 +128,27 @@ export function targetKeyDownEventListener(event: any): boolean {
|
|||
window.open('../main_window/index.html', '_self');
|
||||
event.preventDefault();
|
||||
handledCase = true;
|
||||
key = 'Back';
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return handledCase;
|
||||
return { handledCase: handledCase, key: key };
|
||||
};
|
||||
|
||||
export function targetKeyUpEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
|
||||
return common.targetKeyUpEventListener(event);
|
||||
};
|
||||
|
||||
if (window.webOSAPI.pendingPlay !== null) {
|
||||
onPlay(null, window.webOSAPI.pendingPlay);
|
||||
if (window.webOSAPI.pendingPlay.rendererEvent === 'play-playlist') {
|
||||
onPlayPlaylist(null, window.webOSAPI.pendingPlay.rendererMessage);
|
||||
}
|
||||
else {
|
||||
onPlay(null, window.webOSAPI.pendingPlay.rendererMessage);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
|
|
|
@ -10,7 +10,12 @@
|
|||
<script src="./preload.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="title-icon"></div>
|
||||
<div id="loading-spinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||
<div id="idle-background"></div>
|
||||
<img id="thumbnailImage" />
|
||||
<video id="videoPlayer" autoplay preload="auto"></video>
|
||||
<div id="mediaTitle" class="captionsContainer"></div>
|
||||
<div id="videoCaptions" class="captionsContainer"></div>
|
||||
|
||||
<div id="controls" class="container">
|
||||
|
@ -32,7 +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="volume" class="volume_high iconSize"></div>
|
||||
<div class="volumeContainer">
|
||||
|
|
78
receivers/webos/fcast-receiver/src/viewer/Renderer.ts
Normal file
78
receivers/webos/fcast-receiver/src/viewer/Renderer.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { PlayerControlEvent, playerCtrlStateUpdate, onPlay, onPlayPlaylist, setPlaylistItem, playlistIndex } from 'common/viewer/Renderer';
|
||||
import { RemoteKeyCode } from 'lib/common';
|
||||
import * as common from 'lib/common';
|
||||
|
||||
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
|
||||
let handledCase = false;
|
||||
return handledCase;
|
||||
}
|
||||
|
||||
export function targetKeyDownEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
|
||||
let handledCase = false;
|
||||
let key = '';
|
||||
|
||||
switch (event.keyCode) {
|
||||
case RemoteKeyCode.Stop:
|
||||
// history.back();
|
||||
window.open('../main_window/index.html', '_self');
|
||||
event.preventDefault();
|
||||
handledCase = true;
|
||||
key = 'Stop';
|
||||
break;
|
||||
|
||||
case RemoteKeyCode.Rewind:
|
||||
setPlaylistItem(playlistIndex - 1);
|
||||
event.preventDefault();
|
||||
handledCase = true;
|
||||
key = 'Rewind';
|
||||
break;
|
||||
|
||||
case RemoteKeyCode.Play:
|
||||
playerCtrlStateUpdate(PlayerControlEvent.Play);
|
||||
event.preventDefault();
|
||||
handledCase = true;
|
||||
key = 'Play';
|
||||
break;
|
||||
case RemoteKeyCode.Pause:
|
||||
playerCtrlStateUpdate(PlayerControlEvent.Pause);
|
||||
event.preventDefault();
|
||||
handledCase = true;
|
||||
key = 'Pause';
|
||||
break;
|
||||
|
||||
case RemoteKeyCode.FastForward:
|
||||
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');
|
||||
event.preventDefault();
|
||||
handledCase = true;
|
||||
key = 'Back';
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return { handledCase: handledCase, key: key };
|
||||
};
|
||||
|
||||
export function targetKeyUpEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
|
||||
return common.targetKeyUpEventListener(event);
|
||||
};
|
||||
|
||||
if (window.webOSAPI.pendingPlay !== null) {
|
||||
if (window.webOSAPI.pendingPlay.rendererEvent === 'play-playlist') {
|
||||
onPlayPlaylist(null, window.webOSAPI.pendingPlay.rendererMessage);
|
||||
}
|
||||
else {
|
||||
onPlay(null, window.webOSAPI.pendingPlay.rendererMessage);
|
||||
}
|
||||
}
|
45
receivers/webos/fcast-receiver/src/viewer/index.html
Normal file
45
receivers/webos/fcast-receiver/src/viewer/index.html
Normal file
|
@ -0,0 +1,45 @@
|
|||
<!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/inter.css" />
|
||||
<link rel="stylesheet" href="./common.css" />
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
<script src="../player/preload.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Empty video element as a workaround to fix issue with white border outline without it... -->
|
||||
<video id="idleBackground" class="video"></video>
|
||||
<div id="viewer" class="viewer">
|
||||
<div id="titleIcon"></div>
|
||||
<div id="loadingSpinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||
<img id="viewerImage" class="viewer" />
|
||||
<iframe id="viewerGeneric" class="viewer"></iframe>
|
||||
</div>>
|
||||
|
||||
<div id="controls" class="container">
|
||||
<div id="leftButtonContainer" class="buttonContainer">
|
||||
<div id="mediaTitle"></div>
|
||||
</div>
|
||||
<div id="centerButtonContainer" class="buttonContainer">
|
||||
<div id="playPrevious" class="playPrevious iconSize" style="display: none"></div>
|
||||
<div id="action" class="play iconSize" style="display: none"></div>
|
||||
<div id="playlistLength" style="display: none"></div>
|
||||
<div id="playNext" class="playNext iconSize" style="display: none"></div>
|
||||
</div>
|
||||
|
||||
<!-- <div id="rightButtonContainer" class="buttonContainer">
|
||||
<div id="fullscreen" class="fullscreen_on iconSize"></div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div id="toast-notification">
|
||||
<div id="toast-icon"></div>
|
||||
<div id="toast-text"></div>
|
||||
</div>
|
||||
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
1
receivers/webos/fcast-receiver/src/viewer/style.css
Normal file
1
receivers/webos/fcast-receiver/src/viewer/style.css
Normal file
|
@ -0,0 +1 @@
|
|||
/* Stub for future use */
|
|
@ -18,13 +18,18 @@ module.exports = [
|
|||
preload: './src/main/Preload.ts',
|
||||
renderer: './src/main/Renderer.ts',
|
||||
},
|
||||
target: 'web',
|
||||
target: ['web', 'es5'],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
include: [path.resolve(__dirname, '../../common/web'), path.resolve(__dirname, 'src')],
|
||||
use: [{ loader: 'ts-loader' }]
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
include: [path.resolve(__dirname, 'lib'), path.resolve(__dirname, 'src')],
|
||||
use: [{ loader: 'ts-loader' }]
|
||||
}
|
||||
],
|
||||
},
|
||||
|
@ -88,13 +93,18 @@ module.exports = [
|
|||
preload: './src/player/Preload.ts',
|
||||
renderer: './src/player/Renderer.ts',
|
||||
},
|
||||
target: 'web',
|
||||
target: ['web', 'es5'],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
include: [path.resolve(__dirname, '../../common/web'), path.resolve(__dirname, 'src')],
|
||||
use: [{ loader: 'ts-loader' }]
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
include: [path.resolve(__dirname, 'lib'), path.resolve(__dirname, 'src')],
|
||||
use: [{ loader: 'ts-loader' }]
|
||||
}
|
||||
],
|
||||
},
|
||||
|
@ -130,4 +140,58 @@ module.exports = [
|
|||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
mode: buildMode,
|
||||
entry: {
|
||||
// Player preload is intentionally reused
|
||||
preload: './src/player/Preload.ts',
|
||||
renderer: './src/viewer/Renderer.ts',
|
||||
},
|
||||
target: ['web', 'es5'],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
include: [path.resolve(__dirname, '../../common/web'), path.resolve(__dirname, 'src')],
|
||||
use: [{ loader: 'ts-loader' }]
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
include: [path.resolve(__dirname, 'lib'), path.resolve(__dirname, 'src')],
|
||||
use: [{ loader: 'ts-loader' }]
|
||||
}
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'src': path.resolve(__dirname, 'src'),
|
||||
'lib': path.resolve(__dirname, 'lib'),
|
||||
'modules': path.resolve(__dirname, 'node_modules'),
|
||||
'common': path.resolve(__dirname, '../../common/web'),
|
||||
},
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.resolve(__dirname, 'dist/viewer'),
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: '../../common/web/viewer/common.css',
|
||||
to: '[name][ext]',
|
||||
},
|
||||
{
|
||||
from: './src/viewer/*',
|
||||
to: '[name][ext]',
|
||||
globOptions: { ignore: ['**/*.ts'] }
|
||||
}
|
||||
],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
TARGET: JSON.stringify(TARGET)
|
||||
})
|
||||
]
|
||||
}
|
||||
];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue