diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..33f58e84 --- /dev/null +++ b/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "chrome": "51" + } + } + ] + ] +} \ No newline at end of file diff --git a/config.xml b/config.xml index 25da3585..98c1118c 100644 --- a/config.xml +++ b/config.xml @@ -1,5 +1,5 @@ - + Megacubo An intuitive, free and open source IPTV player. diff --git a/package.json b/package.json index 6a7c9709..03a3cd32 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tv.megacubo.app", "displayName": "Megacubo", - "version": "17.1.5", + "version": "17.1.6", "description": "A intuitive and multi-language IPTV player.", "main": "index.js", "scripts": { diff --git a/www/nodejs-project/app.html b/www/nodejs-project/app.html index 62a89dc2..961bd620 100644 --- a/www/nodejs-project/app.html +++ b/www/nodejs-project/app.html @@ -5,6 +5,12 @@ Megacubo + @@ -28,9 +34,6 @@ -
diff --git a/www/nodejs-project/assets/js/app/app.js b/www/nodejs-project/assets/js/app/app.js index 17f1f859..b066b7a7 100644 --- a/www/nodejs-project/assets/js/app/app.js +++ b/www/nodejs-project/assets/js/app/app.js @@ -1,9 +1,5 @@ var body = $('body'), content = $('#explorer content'), wrap = document.querySelector('#explorer wrap'), wrapper = $(wrap) -if(typeof(window.onerror) != 'function'){ - window.onerror = parent.onerror -} - function parseMomentLocale(content){ let startPos = content.indexOf('moment.defineLocale('), endPos = content.lastIndexOf('return ') if(startPos != -1 && endPos != -1){ @@ -477,7 +473,10 @@ function initApp(){ } }) streamer.on('stop', () => { - if(explorer.modalContainer && explorer.modalContainer.querySelector('#modal-template-option-wait')){ + if(explorer.modalContainer && ( + explorer.modalContainer.querySelector('#modal-template-option-wait') || + explorer.modalContainer.querySelector('#modal-template-option-resume') + )){ explorer.endModal() } menuPlaying(false) diff --git a/www/nodejs-project/assets/js/app/utils.js b/www/nodejs-project/assets/js/app/utils.js index 91bde573..e7c2e81b 100644 --- a/www/nodejs-project/assets/js/app/utils.js +++ b/www/nodejs-project/assets/js/app/utils.js @@ -944,7 +944,7 @@ function hexToRGBA(hex, alpha){ const chunkSize = Math.floor((hex.length - 1) / 3) const hexArr = getChunksFromString(hex.slice(1), chunkSize) const [r, g, b, a] = hexArr.map(convertHexUnitTo256) - return `rgba(${r}, ${g}, ${b}, ${getAlphafloat(a, alpha)})` + return 'rgba('+ [r, g, b, getAlphafloat(a, alpha)].join(', ') +')' } function setupFontDetector(){ diff --git a/www/nodejs-project/assets/js/index/index.js b/www/nodejs-project/assets/js/index/index.js index 3f792e82..4af05858 100644 --- a/www/nodejs-project/assets/js/index/index.js +++ b/www/nodejs-project/assets/js/index/index.js @@ -328,33 +328,33 @@ function fakeUpdateProgress() { }, 1000); } -(window.onerror = function (message, file, line, column, errorObj) { +window.onerror = function (message, file, line, column, errorObj) { let stack = typeof errorObj == 'object' && errorObj !== null && errorObj.stack ? errorObj.stack : traceback(); if (maxAlerts) { maxAlerts--; - if (file && file.startsWith('blob:http://')) { // ignore hls.js errors + if (file && !file.startsWith('blob:http://')) { // ignore hls.js errors alert(message + ' ' + file + ':' + line + ' ' + stack); log(message); } } console.error(errorObj || message, { errorObj, message, file, stack }); return true; -}); +} document.addEventListener('pause', function () { - if (channel) { + if (window.channel) { channel.post('message', ['suspend']); } }); document.addEventListener('resume', function () { - if (channel) { + if (window.channel) { channel.post('message', ['resume']); } }); document.addEventListener('backbutton', function (e) { - if (app) { + if (window.app) { e.preventDefault(); app.postMessage({ action: 'backbutton' }, location.origin); } diff --git a/www/nodejs-project/assets/js/index/video.js b/www/nodejs-project/assets/js/index/video.js index ffdda949..aea75bc7 100644 --- a/www/nodejs-project/assets/js/index/video.js +++ b/www/nodejs-project/assets/js/index/video.js @@ -156,7 +156,7 @@ class VideoControl extends EventEmitter { if(!this.suspendStateChangeReporting) this.emit('state', s, err) } } - load(src, mimetype, cookie, mediatype){ + load(src, mimetype, cookie, mediatype, data){ this.setState('loading') this.suspendStateChangeReporting = true this.current && this.current.unload(true) @@ -166,7 +166,7 @@ class VideoControl extends EventEmitter { let m = mimetype.toLowerCase() if(m.indexOf('mpegurl') != -1){ this.setup('html5h', VideoControlAdapterHTML5HLS) - } else if(m.indexOf('mp2t') != -1 || (src.endsWith('.ts') && mediatype == 'video')){ + } else if(m.indexOf('mp2t') != -1 || (src.endsWith('.ts') && mediatype == 'video') || (data && data.mpegts === true)){ this.setup('html5t', VideoControlAdapterHTML5TS) } else if(m.indexOf('audio/') != -1){ this.setup('html5a', VideoControlAdapterHTML5Audio) diff --git a/www/nodejs-project/assets/js/index/video.ts.js b/www/nodejs-project/assets/js/index/video.ts.js index 68c818df..80fce16d 100644 --- a/www/nodejs-project/assets/js/index/video.ts.js +++ b/www/nodejs-project/assets/js/index/video.ts.js @@ -17,7 +17,7 @@ class VideoControlAdapterHTML5TS extends VideoControlAdapterHTML5Video { this.currentMimetype = mimetype } this.mpegts = mpegts.createPlayer({ - type: 'mse', // could be mse, mpegts, m2ts, flv + type: 'mse', // could be mse, mpegts, m2ts, flv url: this.currentSrc, isLive: type != 'video' }, { @@ -27,10 +27,13 @@ class VideoControlAdapterHTML5TS extends VideoControlAdapterHTML5Video { }) this.mpegts.attachMediaElement(this.object) this.errorListener = err => { - console.error('MPEGTS ERROR', err) - const t = this.time() + const t = this.time() + if(t != this.lastErrorTime) { + this.errorsCount = 0 + } this.errorsCount++ - if(t != this.lastErrorTime && this.errorsCount >= (t > 0 ? 20 : 3)){ + console.error('MPEGTS ERROR', err, this.errorsCount, t != this.lastErrorTime) + if(this.errorsCount >= (t > 0 ? 20 : 3)){ this.emit('error', String(err), true) this.state = '' this.emit('state', '') diff --git a/www/nodejs-project/index.html b/www/nodejs-project/index.html index ca7c0c53..b3ca028d 100644 --- a/www/nodejs-project/index.html +++ b/www/nodejs-project/index.html @@ -10,7 +10,7 @@ height: 100vh; margin: 0; padding: 0; - background-color: black; + background-color: #100927; } html, body, iframe { margin: 0; @@ -29,7 +29,7 @@ background-image: url(./assets/images/default_icon_white.png); background-position: center center; background-repeat: no-repeat; - background-size: 20vh 20vh; + background-size: 25vmin 25vmin; } player { width: 100vw; diff --git a/www/nodejs-project/main.js b/www/nodejs-project/main.js index 973cdc4b..e4f77900 100644 --- a/www/nodejs-project/main.js +++ b/www/nodejs-project/main.js @@ -681,33 +681,38 @@ global.ui.on('get-lang-callback', (locale, timezone, ua, online) => { if(global.cordova) { global.ui.emit('get-lang') } else { - let remoteModuleExternal = parseFloat(process.versions.electron) >= 22 + const remoteModuleExternal = parseFloat(process.versions.electron) >= 22 remoteModuleExternal && require('@electron/remote/main').initialize() const { app, BrowserWindow, globalShortcut } = require('electron') - onexit(() => { - app.quit() - }) - app.on('window-all-closed', () => { - app.quit() - }) + onexit(() => app.quit()) if(remoteModuleExternal){ app.on('browser-window-created', (_, window) => { require('@electron/remote/main').enable(window.webContents) }) } + + const hwDefaultAccelFlags = ` + --use-gl=desktop + --enable-gpu-rasterization + --enable-accelerated-video + --enable-accelerated-video-decode + --enable-accelerated-mjpeg-decode + --enable-native-gpu-memory-buffers + ` + let hwAccelFlags = global.config.get('hw-acceleration') || hwDefaultAccelFlags + hwAccelFlags = hwAccelFlags.replace(new RegExp('[\t\n ]+', 'g'), ' ').trim().split(' ').map(s => s.substr(2)).filter(s => s).map(s => s.split('=')) + hwAccelFlags.forEach(a => app.commandLine.appendSwitch(...a)) + app.commandLine.appendSwitch('no-prefetch') - app.commandLine.appendSwitch('use-gl', 'desktop') app.commandLine.appendSwitch('disable-http-cache') app.commandLine.appendSwitch('disable-websql', 'true') - app.commandLine.appendSwitch('enable-gpu-rasterization') - app.commandLine.appendSwitch('enable-accelerated-video') + app.commandLine.appendSwitch('password-store', 'basic') + app.commandLine.appendSwitch('enable-smooth-scrolling') + app.commandLine.appendSwitch('disable-transparency', 'true') app.commandLine.appendSwitch('disable-site-isolation-trials') - app.commandLine.appendSwitch('enable-accelerated-video-decode') - app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode') - app.commandLine.appendSwitch('enable-native-gpu-memory-buffers') app.commandLine.appendSwitch('enable-experimental-web-platform-features') // audioTracks support app.commandLine.appendSwitch('enable-features', 'PlatformHEVCDecoderSupport') - app.commandLine.appendSwitch('disable-features', 'SitePerProcess') + app.commandLine.appendSwitch('disable-features', 'IsolateOrigins,SitePerProcess,NetworkPrediction') app.whenReady().then(() => { const window = new BrowserWindow({ titleBarStyle: 'hidden', diff --git a/www/nodejs-project/modules/channels/channels.js b/www/nodejs-project/modules/channels/channels.js index b57a9628..30960ab2 100644 --- a/www/nodejs-project/modules/channels/channels.js +++ b/www/nodejs-project/modules/channels/channels.js @@ -1644,22 +1644,13 @@ class Channels extends ChannelsKids { if(!global.lists.activeLists.length){ // one list available on index beyound meta watching list return [global.lists.manager.noListsEntry()] } - const namedGroups = {}, isSeries = type == 'series' - let entries = [], groups = await global.lists.groups(type ? [type] : ['series', 'vod']) - const acpolicy = global.config.get('parental-control') - if(acpolicy == 'remove'){ - groups = global.lists.parentalControl.filter(groups) - } else if(acpolicy == 'only') { - groups = global.lists.parentalControl.only(groups) - } - groups.forEach(group => { + const isSeries = type == 'series' + let groups = await global.lists.groups(type ? [type] : ['series', 'vod']) + const acpolicy = global.config.get('parental-control') + const groupToEntry = group => { const name = group.name - const slug = name.toLowerCase().normalize('NFD').replace(new RegExp('[^a-z0-9]', 'g'), '') - if(typeof(namedGroups[slug]) == 'undefined'){ - namedGroups[slug] = [] - } const details = group.group.split('/').filter(n => n != name).join(' · ') - namedGroups[slug].push({ + return { name, details, type: 'group', @@ -1668,59 +1659,50 @@ class Channels extends ChannelsKids { class: isSeries ? 'entry-cover' : undefined, fa: isSeries ? 'fas fa-play-circle' : undefined, renderer: async () => { - let entries = await global.lists.group(group).catch(console.error) - if(Array.isArray(entries)) { - if(acpolicy == 'block'){ - entries = global.lists.parentalControl.filter(entries) - } - entries = await global.lists.tools.deepify(entries, {source: group.url}) - while(entries.length == 1 && entries[0].type == 'group'){ - if(entries[0].entries){ - entries = entries[0].entries - } else if(typeof(entries[0].renderer) == 'function') { - entries = await entries[0].renderer(entries[0]) - } else if(typeof(entries[0].renderer) == 'string') { - entries = await global.storage.temp.promises.get(entries[0].renderer) - } - } - return entries + return await renderer(group) + } + } + } + const parentalFilter = entries => { + if(acpolicy == 'block'){ + entries = global.lists.parentalControl.filter(entries) + } else if(acpolicy == 'remove'){ + entries = global.lists.parentalControl.filter(entries) + } else if(acpolicy == 'only') { + entries = global.lists.parentalControl.only(entries) + } + return entries + } + const renderer = async group => { + console.error('GROUP='+ JSON.stringify(group)) + let entries = await global.lists.group(group).catch(console.error) + if(Array.isArray(entries)) { + let gentries = (group.entries || []).map(g => groupToEntry(g)) + gentries.push(...entries) + while(entries.length == 1){ + const entry = entries[0] + if(entry.entries){ + entries = entry.entries + } else if(typeof(entry.renderer) == 'function') { + entries = await entry.renderer(entry) + } else if(typeof(entry.renderer) == 'string') { + entries = await global.storage.temp.promises.get(entry.renderer) } else { - process.nextTick(() => global.explorer.back(null, true)) - return [] + break } } - }) - }) - Object.keys(namedGroups).forEach(name => { - if(namedGroups[name].length == 1){ - entries.push(namedGroups[name][0]) + entries = parentalFilter(entries).sortByProp('name') + const deepEntries = await global.lists.tools.deepify(entries, {source: group.url}).catch(console.error) + if(Array.isArray(deepEntries)) { + entries = deepEntries + } + return gentries } else { - let rname = namedGroups[name][0].name || name - let fa = 'fas fa-box-open' - let icon = namedGroups[name].map(g => g.icon).filter(g => g).shift() - entries.push({ - name: rname, - type: 'group', - fa, - icon, - class: isSeries ? 'entry-cover' : undefined, - details: namedGroups[name].details, - renderer: async () => { - const entries = [], already = {} - await Promise.allSettled(namedGroups[name].map(g => g.renderer().then(es => { - es.forEach(e => { - if(typeof(already[e.url]) != 'undefined') return - entries.push(e) - already[e.url] = null - }) - }))) - return await global.lists.tools.deepify(entries, { minPageCount: 8 }) - } - }) + process.nextTick(() => global.explorer.back(null, true)) + return [] } - }) - entries = await global.lists.tools.deepify(entries, { minPageCount: 5 }) - return entries + } + return parentalFilter(groups).map(group => groupToEntry(group)) } async hook(entries, path){ if(!path) { diff --git a/www/nodejs-project/modules/crashlog/client.js b/www/nodejs-project/modules/crashlog/client.js index fd66ddde..659668b4 100644 --- a/www/nodejs-project/modules/crashlog/client.js +++ b/www/nodejs-project/modules/crashlog/client.js @@ -29,19 +29,16 @@ class Crashlog { return val } save(message, file, line, column, errorObj){ - console.warn('IDX', message, file, line, column, errorObj) - let stack = errorObj !== undefined && errorObj !== null ? errorObj.stack : traceback() - if(this.maxAlerts){ - this.maxAlerts-- - alert(message +' '+ file +':'+ line +' '+ stack) - console.error(errorObj || message) - } - app && app.emit('crash', message +' '+ file +':'+ line +' '+ stack) + if(!window.app) return + const stack = errorObj !== undefined && errorObj !== null ? errorObj.stack : traceback() + app.emit('crash', message +' '+ file +':'+ line +' '+ stack) } } var crashlog = new Crashlog() -window.onerror = (...args) => { - crashlog.save(...args) +window.onerror = function (arguments) { + var args = Array.from(arguments) + parent.onerror && parent.onerror.apply(null, args) + crashlog.save(args) return true } diff --git a/www/nodejs-project/modules/diagnostics/diagnostics.js b/www/nodejs-project/modules/diagnostics/diagnostics.js index b8cb5b29..ef39130b 100644 --- a/www/nodejs-project/modules/diagnostics/diagnostics.js +++ b/www/nodejs-project/modules/diagnostics/diagnostics.js @@ -25,26 +25,28 @@ class Diagnostics extends Events { priority: p.priority } }); - const updaterResults = global.lists.loader.results; + const updaterResults = global.lists.loader.results, privateLists = []; ['lists', 'parental-control-terms', 'parental-control-pw', 'premium-license'].forEach(k => delete config[k]) Object.keys(lists).forEach(url => { lists[url].owned = myLists.includes(url) if(lists[url].private){ - const u = url.replace(new RegExp('(://[^/]+/).*'), '$1***'), n = lists[url] - n.url = u - delete lists[url] - lists[u] = n + privateLists.push(url) } }) if(diskSpace && diskSpace.size){ diskSpace.free = global.kbfmt(diskSpace.free) diskSpace.size = global.kbfmt(diskSpace.size) } - return {version, diskSpace, freeMem, config, lists, listsRequesting, updaterResults, processedLists, processing, tuning} + let report = {version, diskSpace, freeMem, config, lists, listsRequesting, updaterResults, processedLists, processing, tuning} + report = JSON.stringify(report, null, 3) + privateLists.forEach(url => { + report = report.replace(url, 'http://***') + }) + return report } async saveReport(){ const file = global.downloads.folder +'/report.txt' - await fs.promises.writeFile(file, JSON.stringify(await this.report(), null, 3), {encoding: 'utf8'}) + await fs.promises.writeFile(file, await this.report(), {encoding: 'utf8'}) global.downloads.serve(file, true, false).catch(global.displayErr) } async checkDisk(){ diff --git a/www/nodejs-project/modules/download/download.js b/www/nodejs-project/modules/download/download.js index a74816fb..ea1fa17d 100644 --- a/www/nodejs-project/modules/download/download.js +++ b/www/nodejs-project/modules/download/download.js @@ -109,6 +109,7 @@ class Download extends Events { const now = global.time() if(this.opts.authURL && (!Download.pingAuthDelay[this.opts.authURL] || now > Download.pingAuthDelay[this.opts.authURL])){ Download.pingAuthDelay[this.opts.authURL] = now + 120 + console.error('PINGAUTHURL: '+ this.opts.authURL +' '+ now) Download.get({ url: this.opts.authURL, timeout: 20, @@ -732,7 +733,7 @@ class Download extends Events { if(this.opts.cacheTTL){ Download.cache.save(this, null, true) // save redirect, before changing currentURL, end it always despite of responseSource } - this.currentURL = global.absolutize(response.headers['location'], this.opts.url) + this.currentURL = global.absolutize(response.headers['location'], this.currentURL) if(this.opts.debug){ console.log('>> Download redirect', this.opts.followRedirect, response.headers['location'], this.currentURL) } diff --git a/www/nodejs-project/modules/lists/index.js b/www/nodejs-project/modules/lists/index.js index 3c563fa4..88e7216c 100644 --- a/www/nodejs-project/modules/lists/index.js +++ b/www/nodejs-project/modules/lists/index.js @@ -492,22 +492,36 @@ class Index extends Common { } } async group(group){ + console.error('GGROUP='+JSON.stringify(group)) + if(!this.lists[group.url]){ throw 'List not loaded' } - let map = this.lists[group.url].index.groups[group.group] +/* + let mmap, map = this.lists[group.url].index.groups[group.group].slice(0) if(!map) map = [] Object.keys(this.lists[group.url].index.groups).forEach(s => { - if(s.indexOf('/') != -1 && s.startsWith(group.group)){ - this.lists[group.url].index.groups[s].forEach(n => map.includes(n) || map.push(n)) + if(s != group.group && s.indexOf('/') != -1 && s.startsWith(group.group)){ + if(!mmap) { // jit + mmap = new Map(map.map(m => [m, null])) + map = [] // freeup mem + } + this.lists[group.url].index.groups[s].forEach(n => mmap.has(n) || mmap.set(n, null)) } }) - let entries = await this.lists[group.url].getEntries(map) + if(mmap) { + map = Array.from(mmap, ([key]) => key) + } +*/ + let entries = [], map = this.lists[group.url].index.groups[group.group] || [] + if(map.length) { + entries = await this.lists[group.url].getEntries(map) + return entries + } entries = this.parentalControl.filter(entries, true) entries = this.sort(entries) - return entries } } diff --git a/www/nodejs-project/modules/lists/list-index-utils.js b/www/nodejs-project/modules/lists/list-index-utils.js index 2deaa7e2..07239eab 100644 --- a/www/nodejs-project/modules/lists/list-index-utils.js +++ b/www/nodejs-project/modules/lists/list-index-utils.js @@ -1,5 +1,6 @@ const fs = require('fs'), Events = require('events') const readline = require('readline'), createReader = require('../reader') +const pLimit = require('p-limit') class ListIndexUtils extends Events { constructor(){ @@ -33,6 +34,7 @@ class ListIndexUtils extends Events { let fd = null try { fd = await fs.promises.open(this.file, 'r') + /* for (const [index, range] of ranges.entries()) { const length = range.end - range.start const buffer = Buffer.alloc(length) @@ -42,6 +44,20 @@ class ListIndexUtils extends Events { } lines[map[index]] = buffer.toString() } + */ + const limit = pLimit(4) + const tasks = ranges.map((r, i) => { + return async () => { + const length = r.end - r.start + const buffer = Buffer.alloc(length) + const { bytesRead } = await fd.read(buffer, 0, length, r.start) + if(bytesRead < buffer.length) { + buffer = buffer.slice(0, bytesRead) + } + lines[map[i]] = buffer.toString() + } + }).map(limit) + await Promise.allSettled(tasks) } catch (error) { console.error(error) } finally { diff --git a/www/nodejs-project/modules/lists/parser.js b/www/nodejs-project/modules/lists/parser.js index eacc38d2..407f9fa6 100644 --- a/www/nodejs-project/modules/lists/parser.js +++ b/www/nodejs-project/modules/lists/parser.js @@ -309,7 +309,7 @@ class Parser extends EventEmitter { if (s.length == 3 && s.toLowerCase().trim() === 'n/a') { return '' } - if(s.match(Parser.regexes['group-separators'], '/')) { // if there are few cases, is better not replace directly + if(s.match(Parser.regexes['group-separators'])) { // if there are few cases, is better not replace directly s = s.replace(Parser.regexes['group-separators'], '/') } if(s.indexOf('[') != -1) { @@ -398,7 +398,7 @@ class Parser extends EventEmitter { } Parser.regexes = { - 'group-separators': new RegExp('([\\\\|;]| /|/ )', 'g'), + 'group-separators': new RegExp('( ?[\\\\|;] ?| /|/ )', 'g'), 'notags': new RegExp('\\[[^\\]]*\\]', 'g'), 'between-brackets': new RegExp('\\[[^\\]]*\\]', 'g'), // match data between brackets 'accents': new RegExp('[\\u0300-\\u036f]', 'g'), // match accents diff --git a/www/nodejs-project/modules/options/options.js b/www/nodejs-project/modules/options/options.js index 9d246aa5..ecf38d17 100644 --- a/www/nodejs-project/modules/options/options.js +++ b/www/nodejs-project/modules/options/options.js @@ -347,49 +347,7 @@ class OptionsExportImport extends PerformanceProfiles { } } -class OptionsHardwareAcceleration extends OptionsExportImport { - constructor(){ - super() - this.hwaDisableFlags = ['--disable-gpu', '--force-cpu-draw'] - } - async setHardwareAcceleration(enable){ - let hasErr - const fs = require('fs'), file = APPDIR +'/package.json' - const ret = await fs.promises.access(file, fs.constants.W_OK).catch(err => { - hasErr = err - }) - if(hasErr){ - global.explorer.dialog([ - {template: 'question', text: global.MANIFEST.window.title, fa: 'fas fa-info-circle'}, - {template: 'message', text: 'You must run '+ global.MANIFEST.window.title +' as admin to change this option.'}, - {template: 'option', text: 'OK', id: 'ok'} - ], 'ok').catch(console.error) // dont wait - global.explorer.refreshNow() - } else { - let manifest = await fs.promises.readFile(file) - manifest = global.parseJSON(manifest) - this.hwaDisableFlags.forEach(flag => { - manifest['chromium-args'] = manifest['chromium-args'].replace(flag, '').trim() - }); - if(!enable){ - this.hwaDisableFlags.forEach((flag) => { - manifest['chromium-args'] += ' '+ flag - }) - } - await fs.promises.writeFile(file, JSON.stringify(manifest, null, 3)) - global.energy.restart() - } - } - getHardwareAcceleration(){ - const fs = require('fs') - let manifest = String(fs.readFileSync(global.APPDIR +'/package.json')) - return !this.hwaDisableFlags.every(flag => { - return manifest.indexOf(flag) != -1 - }) - } -} - -class OptionsP2P extends OptionsHardwareAcceleration { +class OptionsP2P extends OptionsExportImport { constructor(){ super() this.p2pDebugShowing = false @@ -1348,11 +1306,20 @@ class Options extends OptionsP2P { ] if(!global.cordova){ opts[opts.length - 1].entries.push({ - name: 'GPU rendering', type: 'check', action: (data, checked) => { - this.setHardwareAcceleration(checked).catch(global.displayErr) - }, checked: () => { - return this.getHardwareAcceleration() - }}) + name: 'GPU rendering', type: 'check', + action: (data, checked) => { + let value = '' // auto, enabled + if(!checked) { + value = '--disable-gpu --force-cpu-draw' + } + global.config.set('hw-acceleration', value) + global.energy.restart() + }, + checked: () => { + const value = global.config.get('hw-acceleration') + return value.indexOf('--disable-gpu') == -1 + } + }) } return opts }}, diff --git a/www/nodejs-project/modules/streamer/client.js b/www/nodejs-project/modules/streamer/client.js index 4a1a83a8..ab1e11d7 100644 --- a/www/nodejs-project/modules/streamer/client.js +++ b/www/nodejs-project/modules/streamer/client.js @@ -156,7 +156,7 @@ class StreamerCasting extends StreamerOSD { if(parent.player.state){ parent.player.resume() } else { - parent.player.load(this.activeSrc, this.activeMimetype, this.activeCookie, this.activeMediatype) + parent.player.load(this.activeSrc, this.activeMimetype, this.activeCookie, this.activeMediatype, this.data) this.stateListener('loading') } } @@ -1553,7 +1553,7 @@ class StreamerClientController extends StreamerClientControls { this.activeMimetype = (mimetype || '').toLowerCase() this.activeMediatype = mediatype this.inLiveStream = this.activeMediatype == 'live' - parent.player.load(src, mimetype, cookie, this.activeMediatype) + parent.player.load(src, mimetype, cookie, this.activeMediatype, this.data) this.emit('start') } stop(fromServer){ diff --git a/www/nodejs-project/modules/streamer/streamer.js b/www/nodejs-project/modules/streamer/streamer.js index b93ef532..34c8599b 100644 --- a/www/nodejs-project/modules/streamer/streamer.js +++ b/www/nodejs-project/modules/streamer/streamer.js @@ -336,21 +336,21 @@ class StreamerBase extends StreamerTools { } } }) - intent.on('streamer-connect', () => this.connect()) + intent.on('streamer-connect', () => this.uiConnect()) if(intent.codecData){ intent.emit('codecData', intent.codecData) } this.emit('commit', intent) intent.emit('commit') - let data = this.connect(intent) - console.warn('STREAMER COMMIT '+ data.url) + this.uiConnect(intent) + console.warn('STREAMER COMMIT '+ intent.data.url) return true } } else { return 'NO INTENT' } } - connect(intent){ + uiConnect(intent, skipTranscodingCheck){ if(!intent) intent = this.active let data = intent.data data.engine = intent.type @@ -361,11 +361,11 @@ class StreamerBase extends StreamerTools { data.icon = global.icons.url + global.channels.entryTerms(data).join(',') } this.emit('streamer-connect', intent.endpoint, intent.mimetype, data) - if(intent.transcoderStarting){ + if(!skipTranscodingCheck && intent.transcoderStarting){ global.ui.emit('streamer-connect-suspend') - } - if(!this.opts.shadow){ - global.osd.hide('streamer') + if(!this.opts.shadow){ + global.osd.hide('streamer') + } } return data } @@ -399,7 +399,7 @@ class StreamerBase extends StreamerTools { global.ui.emit('transcode-starting', true) } intent.transcode().then(() => { - this.emit('streamer-connect', intent.endpoint, intent.mimetype, intent.data) + this.uiConnect(intent, true) cb(null, intent.transcoder) }).catch(err => { if(this.active){ @@ -711,7 +711,7 @@ class StreamerTracks extends StreamerGoNext { }) if(uri && uri != this.active.endpoint){ this.active.endpoint = uri - this.connect() + this.uiConnect() } } return {ret, opts} diff --git a/www/nodejs-project/modules/supercharge/supercharge-node.js b/www/nodejs-project/modules/supercharge/supercharge-node.js new file mode 100644 index 00000000..e925e2c5 --- /dev/null +++ b/www/nodejs-project/modules/supercharge/supercharge-node.js @@ -0,0 +1,234 @@ +function patch(scope) { + if(typeof(require) == 'function'){ + if(typeof(scope.URL) != 'undefined'){ // node + scope.URL = require('url').URL + } + if(typeof(scope.URLSearchParams) == 'undefined'){ // node + scope.URLSearchParams = require('url-search-params-polyfill') + } + } + scope.isWritable = stream => { + return (stream.writable || stream.writeable) && !stream.finished + } + scope.checkDirWritePermission = async dir => { + const file = dir +'/temp.txt', fsp = scope.getFS().promises + await fsp.writeFile(file, '0') + await fsp.unlink(file) + return true + } + scope.checkDirWritePermissionSync = dir => { + let fine + const file = dir +'/temp.txt', fs = scope.getFS() + try { + fs.writeFileSync(file, '0') + fine = true + fs.unlinkSync(file) + } catch(e) { + console.error(e) + } + return fine + } + scope.rmdir = (folder, itself, cb) => { + const rimraf = require('rimraf') + let dir = folder + if(dir.charAt(dir.length - 1) == '/'){ + dir = dir.substr(0, dir.length - 1) + } + if(!itself){ + dir += '/*' + } + if(cb === true){ // sync + try { + rimraf.sync(dir) + } catch(e) {} + } else { + try { + rimraf(dir, cb || (() => {})) + } catch(e) { + if(typeof(cb) == 'function'){ + cb() + } + } + } + } + scope.getFS = () => { + if(!scope.__fs){ + scope.__fs = require('fs') + } + return scope.__fs + } + scope._moveFile = async (from, to) => { + const fs = scope.getFS() + const fstat = await fs.promises.stat(from).catch(console.error) + if(!fstat) throw '"from" file not found' + let err + await fs.promises.copyFile(from, to).catch(e => err = e) + let tstat + if(typeof(err) != 'undefined'){ + tstat = await fs.promises.stat(to).catch(console.error) + if(tstat && tstat.size == fstat.size){ + err = null + } + } + if(err){ + throw err + } + fs.promises.unlink(from).catch(() => {}) + return true + } + scope.moveFile = (from, to, _cb, timeout=5, until=null, startedAt = null, fromSize=null) => { + const fs = scope.getFS(), now = scope.time(), cb = () => { + if(_cb){ + _cb() + _cb = null + } + } + if(until === null){ + until = now + timeout + } + if(startedAt === null){ + startedAt = now + } + const move = () => { + scope._moveFile(from, to).then(() => cb()).catch(err => { + if(until <= now){ + fs.access(from, (err, stat) => { + console.error('MOVERETRY GAVEUP AFTER '+ (now - startedAt) +' SECONDS', err, fromSize, err) + return cb(err) + }) + return + } + fs.stat(to, (ferr, stat) => { + if(stat && stat.size == fromSize){ + cb() + } else { + fs.stat(from, (err, stat) => { + if(stat && stat.size == fromSize){ + setTimeout(() => { + scope.moveFile(from, to, cb, timeout, until, startedAt, fromSize) + }, 500) + } else { + console.error('MOVERETRY FROM FILE WHICH DOESNT EXISTS ANYMORE', err, stat) + console.error(ferr, err) + cb(err || '"from" file changed') + } + }) + } + }) + }) + } + if(fromSize === null){ + fs.stat(from, (err, stat) => { + if(err){ + console.error('MOVERETRY FROM FILE NEVER EXISTED', err) + cb(err) + } else { + fromSize = stat.size + move() + } + }) + } else { + move() + } + } + scope.execSync = cmd => { + let stdout + try { + stdout = require('child_process').execSync(cmd) + } catch(e) { + stdout = String(e) + } + return String(stdout) + } + scope.isNetworkIP = addr => { + if(addr){ + if(addr.startsWith('10.') || addr.startsWith('172.') || addr.startsWith('192.')){ + return 'ipv4' + } + } + } + scope.androidSDKVer = () => { + if(!scope.androidSDKVerCache){ + scope.androidSDKVerCache = parseInt(scope.execSync('getprop ro.build.version.sdk').trim()) + } + return scope.androidSDKVerCache + } + scope.os = () => { + if(!scope.osCache){ + scope.osCache = require('os') + } + return scope.osCache + } + scope.networkIpCache = false + scope.networkIpCacheTTL = 10 + scope.networkDummyInterfaces = addr => { + return { + "Wi-Fi": [ + { + "address": addr, + "netmask": "255.255.255.0", + "family": "IPv4", + "mac": "00:00:00:00:00:00", + "internal": false + } + ], + "Loopback Pseudo-Interface 1": [ + { + "address": "127.0.0.1", + "netmask": "255.0.0.0", + "family": "IPv4", + "mac": "00:00:00:00:00:00", + "internal": true, + "cidr": "127.0.0.1/8" + } + ] + } + } + scope.androidIPCommand = () => { + return scope.execSync('ip route get 8.8.8.8') + } + scope.networkInterfaces = () => { + if(process.platform == 'android'){ + let sdkVer = scope.androidSDKVer() + if(isNaN(sdkVer) || sdkVer < 20 || sdkVer >= 29){ // keep "sdkVer < x" check + // on most recent sdks, os.networkInterces() crashes nodejs-mobile-cordova with a uv_interface_addresses error + let addr, time = scope.time() + if(scope.networkIpCache && (scope.networkIpCache.time + scope.networkIpCacheTTL) > time){ + addr = scope.networkIpCache.addr + } else { + addr = scope.androidIPCommand().match(new RegExp('src +([0-9\.]+)')) + if(addr){ + addr = addr[1] + scope.networkIpCache = {addr, time} + } else { + addr = scope.networkIpCache ? scope.networkIpCache.addr : '127.0.0.1' + } + } + return scope.networkDummyInterfaces(addr) + } + } + return scope.os().networkInterfaces() + } + scope.networkIP = () => { + let interfaces = scope.networkInterfaces(), addr = '127.0.0.1', skipIfs = new RegExp('(vmware|virtualbox)', 'i') + for (let devName in interfaces) { + if(devName.match(skipIfs)) continue + let iface = interfaces[devName] + for (let i = 0; i < iface.length; i++) { + let alias = iface[i] + if (alias.family === 'IPv4' && !alias.internal && scope.isNetworkIP(alias.address)){ + addr = alias.address + } + } + } + return addr + } +} + +if(typeof(module) != 'undefined' && typeof(module.exports) != 'undefined'){ + module.exports = patch +} else { + patch(window) +} + + diff --git a/www/nodejs-project/modules/supercharge/supercharge.js b/www/nodejs-project/modules/supercharge/supercharge.js index 2702f831..cf124353 100644 --- a/www/nodejs-project/modules/supercharge/supercharge.js +++ b/www/nodejs-project/modules/supercharge/supercharge.js @@ -1,604 +1,645 @@ -function patch(scope){ - if (!scope.String.prototype.format) { - Object.defineProperty(String.prototype, 'format', { - enumerable: false, - configurable: false, - writable: false, - value: function (){ - var args = arguments; - return this.replace(/{(\d+)}/g, function(match, number) { - return typeof args[number] != 'undefined' - ? args[number] - : match - }) - } - }) - } - if (!scope.String.prototype.matchAll) { - Object.defineProperty(String.prototype, 'matchAll', { - enumerable: false, - configurable: false, - writable: false, - value: function(regexp) { - var matches = [] - this.replace(regexp, function() { - var arr = ([]).slice.call(arguments, 0) - var extras = arr.splice(-2) - arr.index = extras[0] - arr.input = extras[1] - matches.push(arr) - }) - return matches.length ? matches : [] - } - }) - } - if (!scope.Array.prototype.findLastIndex) { - Object.defineProperty(scope.Array.prototype, 'findLastIndex', { - enumerable: false, - configurable: false, - writable: false, - value: function (callback, thisArg) { - for (let i = this.length - 1; i >= 0; i--) { - if (callback.call(thisArg, this[i], i, this)) return i; - } - return -1; - } - }) - } - if (!scope.Array.prototype.unique) { - Object.defineProperty(scope.Array.prototype, 'unique', { - enumerable: false, - configurable: false, - writable: false, - value: function() { - var arr = [] - for (var i = 0; i < this.length; i++) { - if (arr.indexOf(this[i]) == -1){ - arr.push(this[i]) - } - } - return arr - } - }) - } - if (!scope.Array.prototype.sortByProp) { - Object.defineProperty(scope.Array.prototype, 'sortByProp', { - enumerable: false, - configurable: false, - writable: false, - value: function (p, reverse) { - if(Array.isArray(this)){ // this.slice is not a function (?!) - return this.slice(0).sort((a,b) => { - let ua = typeof(a[p]) == 'undefined', ub = typeof(b[p]) == 'undefined' - if(ua && ub) return 0 - if(ua && !ub) return reverse ? 1 : -1 - if(!ua && ub) return reverse ? -1 : 1 - if(reverse) return (a[p] > b[p]) ? -1 : (a[p] < b[p]) ? 1 : 0; - return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0; - }) - } - return this - } - }) - } - if (!scope.Number.prototype.between) { - Object.defineProperty(Number.prototype, 'between', { - enumerable: false, - configurable: false, - writable: false, - value: function(a, b) { - var min = Math.min(a, b), max = Math.max(a, b) - return this >= min && this <= max - } - }) - } - if (!scope.String.prototype.replaceAll) { - Object.defineProperty(String.prototype, 'replaceAll', { - enumerable: false, - configurable: false, - writable: false, - value: function(search, replacement) { - let target = this - if(target.indexOf(search)!=-1){ - target = target.split(search).join(replacement) - } - return String(target) - } - }) - } - scope.validateURL = url => { - if(url && url.length > 11){ - const parts = url.match(new RegExp('^(https?://|//)[A-Za-z0-9_\\-\\.\\:@]{4,}', 'i')) - return parts && parts.length - } - } - scope.decodeURIComponentSafe = uri => { - try { - return decodeURIComponent(uri) - } catch(e) { - return uri.replace(new RegExp('%[A-Z0-9]{0,2}', 'gi'), x => { - try { - return decodeURIComponent(x) - } catch(e) { - return x - } - }) - } - } - if(typeof(require) == 'function'){ - if(typeof(scope.URL) != 'undefined'){ // node - scope.URL = require('url').URL - } - if(typeof(scope.URLSearchParams) == 'undefined'){ // node - scope.URLSearchParams = require('url-search-params-polyfill') - } - } - scope.deepClone = (from, allowNonSerializable) => { - if (from == null || typeof from != "object") return from - if (from.constructor != Object && from.constructor != Array) return from - if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function || - from.constructor == String || from.constructor == Number || from.constructor == Boolean) - return new from.constructor(from) - let to = new from.constructor() - for (var name in from){ - if(allowNonSerializable || ['string', 'object', 'number', 'boolean'].includes(typeof(from[name]))){ - to[name] = typeof to[name] == "undefined" ? scope.deepClone(from[name], allowNonSerializable) : to[name] - } - } - return to - } - scope.kfmt = (num, digits) => { - var si = [ - { value: 1, symbol: "" }, - { value: 1E3, symbol: "K" }, - { value: 1E6, symbol: "M" }, - { value: 1E9, symbol: "G" }, - { value: 1E12, symbol: "T" }, - { value: 1E15, symbol: "P" }, - { value: 1E18, symbol: "E" } - ] - var i, rx = /\.0+$|(\.[0-9]*[1-9])0+$/ - for (i = si.length - 1; i > 0; i--) { - if (num >= si[i].value) { - break - } - } - return (num / si[i].value).toFixed(digits).replace(rx, "$1") + si[i].symbol - } - scope.kbfmt = (bytes, decimals = 2) => { // https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript - if (isNaN(bytes) || typeof(bytes) != 'number') return 'N/A' - if (bytes === 0) return '0 Bytes' - const k = 1024, dm = decimals < 0 ? 0 : decimals, sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] - } - scope.kbsfmt = (bytes, decimals = 1) => { // https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript - if (isNaN(bytes) || typeof(bytes) != 'number') return 'N/A' - if (bytes === 0) return '0 Bytes/ps' - const k = 1024, dm = decimals < 0 ? 0 : decimals, sizes = ['Bytes/ps', 'KBps', 'MBps', 'GBps', 'TBps', 'PBps', 'EBps', 'ZBps', 'YBps'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] - } - scope.componentToHex = (c) => { - var hex = c.toString(16); - return hex.length == 1 ? '0' + hex : hex - } - scope.rgbToHex = (r, g, b) => { - return '#'+ scope.componentToHex(r) + scope.componentToHex(g) + scope.componentToHex(b) - } - scope.hexToRgb = ohex => { - var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i, hex = ohex.replace(shorthandRegex, (m, r, g, b) => { - return r + r + g + g + b + b - }) - var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16) - } : ohex - } - scope.ucWords = (str, force) => { - if(!force && str != str.toLowerCase()){ - return str - } - return str.replace(new RegExp('(^|[ ])[A-zÀ-ú]', 'g'), letra => { - return letra.toUpperCase() - }) - } - scope.ucFirst = (str, keepCase) => { - if(!keepCase){ - str = str.toLowerCase() - } - return str.replace(/^[\u00C0-\u1FFF\u2C00-\uD7FF\w]/g, letter => { - return letter.toUpperCase() - }) - } - scope.getArrayMax = arr => { // https://stackoverflow.com/questions/42623071/maximum-call-stack-size-exceeded-with-math-min-and-math-max - let len = arr.length - let max = -Infinity - while (len--) { - if(arr[len] > max) max = arr[len] - } - return max - } - scope.getArrayMin = arr => { - let len = arr.length - let min = Infinity - while (len--) { - if(arr[len] < min) min = arr[len] - } - return max - } - scope.hmsClockToSeconds = str => { - var cs = str.split('.'), p = cs[0].split(':'), s = 0, m = 1; - while (p.length > 0) { - s += m * parseInt(p.pop(), 10); - m *= 60; - } - if(cs.length > 1 && cs[1].length >= 2){ - s += parseInt(cs[1].substr(0, 2)) / 100; - } - return s - } - scope.hmsSecondsToClock = secs => { - var sec_num = parseInt(secs, 10); // don't forget the second param - var hours = Math.floor(sec_num / 3600); - var minutes = Math.floor((sec_num - (hours * 3600)) / 60); - var seconds = sec_num - (hours * 3600) - (minutes * 60); - if (hours < 10) {hours = "0"+hours;} - if (minutes < 10) {minutes = "0"+minutes;} - if (seconds < 10) {seconds = "0"+seconds;} - return hours+':'+minutes+':'+seconds; - } - scope.ts2clock = time => { - let locale = undefined, timezone = undefined - if(typeof(time) == 'string'){ - time = parseInt(time) - } - time = global.moment(time * 1000) - return time.format('LT') - } - scope.getUniqueFilenameHelper = (name, i) => { - let pos = name.lastIndexOf('.') - if(pos == -1){ - return name + '-' + i - } else { - return name.substr(0, pos) + '-' + i + name.substr(pos) - } - } - scope.getUniqueFilename = (files, name) => { - let i = 0, nname = name - while(files.includes(nname)){ - i++ - nname = scope.getUniqueFilenameHelper(name, i) - } - return nname - } - scope.traceback = () => { - try { - var a = {} - a.debug() - } catch(ex) { - return ex.stack.replace('TypeError: a.debug is not a function', '').trim() - } - } - scope.forwardSlashes = path => { - if(path && path.indexOf('\\') != -1){ - return path.replaceAll('\\', '/').replaceAll('//', '/') - } - return path - } - scope.absolutize = (path, url) => { - if(!path) return url - if(!url) return path - if(path.startsWith('//')){ - path = 'http:'+ path - } - if(path.match(new RegExp('^[htps:]?//'))){ - return path +function patch(scope) { + if (!scope.String.prototype.format) { + Object.defineProperty(String.prototype, 'format', { + enumerable: false, + configurable: false, + writable: false, + value: function () { + var args = arguments; + return this.replace(/{(\d+)}/g, function (match, number) { + return typeof args[number] != 'undefined' ? args[number] : match; + }); + } + }); + } + if (!scope.String.prototype.matchAll) { + Object.defineProperty(String.prototype, 'matchAll', { + enumerable: false, + configurable: false, + writable: false, + value: function (regexp) { + var matches = []; + this.replace(regexp, function () { + var arr = [].slice.call(arguments, 0); + var extras = arr.splice(-2); + arr.index = extras[0]; + arr.input = extras[1]; + matches.push(arr); + }); + return matches.length ? matches : []; + } + }); + } + if (!scope.Array.prototype.findLastIndex) { + Object.defineProperty(scope.Array.prototype, 'findLastIndex', { + enumerable: false, + configurable: false, + writable: false, + value: function (callback, thisArg) { + for (let i = this.length - 1; i >= 0; i--) { + if (callback.call(thisArg, this[i], i, this)) return i; } - let uri - try { - uri = new URL(path, url) - return uri.href - } catch(e) { - return global.joinPath(url, path) - } - } - scope.joinPath = (folder, file) => { - if(!file) return folder - if(!folder) return file - let ffolder = folder - let ffile = file - if(ffolder.indexOf('\\') != -1) { - ffolder = scope.forwardSlashes(ffolder) - } - if(ffile.indexOf('\\') != -1) { - ffile = scope.forwardSlashes(ffile) - } - let folderEndsWithSlash = ffolder.charAt(ffolder.length - 1) == '/' - let fileStartsWithSlash = ffile.charAt(0) == '/' - if(fileStartsWithSlash && folderEndsWithSlash) { - ret = ffolder + ffile.substr(1) - } else if(fileStartsWithSlash || folderEndsWithSlash) { - ret = ffolder + ffile - } else { - ret = ffolder +'/'+ ffile - } - return ret - } - scope.time = () => { - return Date.now() / 1000 - } - scope.isVODM3U8 = (content, contentLength) => { - let sample = String(content).toLowerCase() - if(sample.indexOf('#ext-x-playlist-type:vod') != -1) return true - if(sample.match(new RegExp('#ext-x-media-sequence:0[^0-9]'))) return true - let pe = sample.indexOf('#ext-x-endlist') - let px = sample.lastIndexOf('#extinf') - if(pe != -1){ - return pe > px - } - if(sample.indexOf('#ext-x-program-date-time') == -1){ - let pieces = sample.split('#extinf') - if(pieces.length > 30){ - return true - } - if(typeof(contentLength) == 'number' && pieces.length > 2){ // at least 3 pieces, to ensure that the first extinf is complete - let header = pieces.shift() - let pieceLen = pieces[0].length + 7 - let totalEstimatedPieces = (contentLength - header.length) / pieceLen - if(totalEstimatedPieces > 30){ - return true - } - } - } - } - scope.filenameFromURL = (url, defaultExt = 'mp4') => { - let filename = url.split('?')[0].split('/').filter(s => s).pop() - if(!filename || filename.indexOf('=') != -1){ - filename = 'video' - } - if(filename.indexOf('.') == -1){ - filename += '.' + defaultExt - } - return scope.sanitize(filename) - } - scope.isWritable = stream => { - return (stream.writable || stream.writeable) && !stream.finished - } - scope.checkDirWritePermission = async dir => { - const file = dir +'/temp.txt', fsp = scope.getFS().promises - await fsp.writeFile(file, '0') - await fsp.unlink(file) - return true - } - scope.checkDirWritePermissionSync = dir => { - let fine - const file = dir +'/temp.txt', fs = scope.getFS() + return -1; + } + }); + } + if (!scope.Array.prototype.unique) { + Object.defineProperty(scope.Array.prototype, 'unique', { + enumerable: false, + configurable: false, + writable: false, + value: function () { + var arr = []; + for (var i = 0; i < this.length; i++) { + if (arr.indexOf(this[i]) == -1) { + arr.push(this[i]); + } + } + return arr; + } + }); + } + if (!scope.Array.prototype.sortByProp) { + Object.defineProperty(scope.Array.prototype, 'sortByProp', { + enumerable: false, + configurable: false, + writable: false, + value: function (p, reverse) { + if (Array.isArray(this)) { + // this.slice is not a function (?!) + return this.slice(0).sort((a, b) => { + let ua = typeof a[p] == 'undefined', + ub = typeof b[p] == 'undefined'; + if (ua && ub) return 0; + if (ua && !ub) return reverse ? 1 : -1; + if (!ua && ub) return reverse ? -1 : 1; + if (reverse) return a[p] > b[p] ? -1 : a[p] < b[p] ? 1 : 0; + return a[p] > b[p] ? 1 : a[p] < b[p] ? -1 : 0; + }); + } + return this; + } + }); + } + if (!scope.Number.prototype.between) { + Object.defineProperty(Number.prototype, 'between', { + enumerable: false, + configurable: false, + writable: false, + value: function (a, b) { + var min = Math.min(a, b), + max = Math.max(a, b); + return this >= min && this <= max; + } + }); + } + if (!scope.String.prototype.replaceAll) { + Object.defineProperty(String.prototype, 'replaceAll', { + enumerable: false, + configurable: false, + writable: false, + value: function (search, replacement) { + let target = this; + if (target.indexOf(search) != -1) { + target = target.split(search).join(replacement); + } + return String(target); + } + }); + } + scope.validateURL = url => { + if (url && url.length > 11) { + const parts = url.match(new RegExp('^(https?://|//)[A-Za-z0-9_\\-\\.\\:@]{4,}', 'i')); + return parts && parts.length; + } + }; + scope.decodeURIComponentSafe = uri => { + try { + return decodeURIComponent(uri); + } catch (e) { + return uri.replace(new RegExp('%[A-Z0-9]{0,2}', 'gi'), x => { try { - fs.writeFileSync(file, '0') - fine = true - fs.unlinkSync(file) - } catch(e) { - console.error(e) - } - return fine - } - scope.rmdir = (folder, itself, cb) => { - const rimraf = require('rimraf') - let dir = folder - if(dir.charAt(dir.length - 1) == '/'){ - dir = dir.substr(0, dir.length - 1) - } - if(!itself){ - dir += '/*' - } - if(cb === true){ // sync - try { - rimraf.sync(dir) - } catch(e) {} - } else { - try { - rimraf(dir, cb || (() => {})) - } catch(e) { - if(typeof(cb) == 'function'){ - cb() - } - } - } - } - scope.dirname = _path => { - let parts = _path.replace(new RegExp('\\\\', 'g'), '/').split('/') - parts.pop() - return parts.join('/') - } - scope.getFS = () => { - if(!scope.__fs){ - scope.__fs = require('fs') - } - return scope.__fs - } - scope._moveFile = async (from, to) => { - const fs = scope.getFS() - const fstat = await fs.promises.stat(from).catch(console.error) - if(!fstat) throw '"from" file not found' - let err - await fs.promises.copyFile(from, to).catch(e => err = e) - let tstat - if(typeof(err) != 'undefined'){ - tstat = await fs.promises.stat(to).catch(console.error) - if(tstat && tstat.size == fstat.size){ - err = null - } - } - if(err){ - throw err - } - fs.promises.unlink(from).catch(() => {}) - return true - } - scope.moveFile = (from, to, _cb, timeout=5, until=null, startedAt = null, fromSize=null) => { - const fs = scope.getFS(), now = scope.time(), cb = () => { - if(_cb){ - _cb() - _cb = null - } - } - if(until === null){ - until = now + timeout - } - if(startedAt === null){ - startedAt = now - } - const move = () => { - scope._moveFile(from, to).then(() => cb()).catch(err => { - if(until <= now){ - fs.access(from, (err, stat) => { - console.error('MOVERETRY GAVEUP AFTER '+ (now - startedAt) +' SECONDS', err, fromSize, err) - return cb(err) - }) - return - } - fs.stat(to, (ferr, stat) => { - if(stat && stat.size == fromSize){ - cb() - } else { - fs.stat(from, (err, stat) => { - if(stat && stat.size == fromSize){ - setTimeout(() => { - scope.moveFile(from, to, cb, timeout, until, startedAt, fromSize) - }, 500) - } else { - console.error('MOVERETRY FROM FILE WHICH DOESNT EXISTS ANYMORE', err, stat) - console.error(ferr, err) - cb(err || '"from" file changed') - } - }) - } - }) - }) - } - if(fromSize === null){ - fs.stat(from, (err, stat) => { - if(err){ - console.error('MOVERETRY FROM FILE NEVER EXISTED', err) - cb(err) - } else { - fromSize = stat.size - move() - } - }) - } else { - move() - } - } - scope.execSync = cmd => { - let stdout - try { - stdout = require('child_process').execSync(cmd) - } catch(e) { - stdout = String(e) - } - return String(stdout) - } - scope.isNetworkIP = addr => { - if(addr){ - if(addr.startsWith('10.') || addr.startsWith('172.') || addr.startsWith('192.')){ - return 'ipv4' - } - } - } - scope.androidSDKVer = () => { - if(!scope.androidSDKVerCache){ - scope.androidSDKVerCache = parseInt(scope.execSync('getprop ro.build.version.sdk').trim()) - } - return scope.androidSDKVerCache - } - scope.os = () => { - if(!scope.osCache){ - scope.osCache = require('os') - } - return scope.osCache - } - scope.networkIpCache = false - scope.networkIpCacheTTL = 10 - scope.networkDummyInterfaces = addr => { - return { - "Wi-Fi": [ - { - "address": addr, - "netmask": "255.255.255.0", - "family": "IPv4", - "mac": "00:00:00:00:00:00", - "internal": false - } - ], - "Loopback Pseudo-Interface 1": [ - { - "address": "127.0.0.1", - "netmask": "255.0.0.0", - "family": "IPv4", - "mac": "00:00:00:00:00:00", - "internal": true, - "cidr": "127.0.0.1/8" - } - ] - } - } - scope.androidIPCommand = () => { - return scope.execSync('ip route get 8.8.8.8') - } - scope.networkInterfaces = () => { - if(process.platform == 'android'){ - let sdkVer = scope.androidSDKVer() - if(isNaN(sdkVer) || sdkVer < 20 || sdkVer >= 29){ // keep "sdkVer < x" check - // on most recent sdks, os.networkInterces() crashes nodejs-mobile-cordova with a uv_interface_addresses error - let addr, time = scope.time() - if(scope.networkIpCache && (scope.networkIpCache.time + scope.networkIpCacheTTL) > time){ - addr = scope.networkIpCache.addr - } else { - addr = scope.androidIPCommand().match(new RegExp('src +([0-9\.]+)')) - if(addr){ - addr = addr[1] - scope.networkIpCache = {addr, time} - } else { - addr = scope.networkIpCache ? scope.networkIpCache.addr : '127.0.0.1' - } - } - return scope.networkDummyInterfaces(addr) - } - } - return scope.os().networkInterfaces() - } - scope.networkIP = () => { - let interfaces = scope.networkInterfaces(), addr = '127.0.0.1', skipIfs = new RegExp('(vmware|virtualbox)', 'i') - for (let devName in interfaces) { - if(devName.match(skipIfs)) continue - let iface = interfaces[devName] - for (let i = 0; i < iface.length; i++) { - let alias = iface[i] - if (alias.family === 'IPv4' && !alias.internal && scope.isNetworkIP(alias.address)){ - addr = alias.address - } - } - } - return addr - } - scope.parseJSON = json => { // prevent JSON related crashes - let ret - try { - let parsed = JSON.parse(json) - ret = parsed - } catch(e) { } - return ret - } + return decodeURIComponent(x); + } catch (e) { + return x; + } + }); + } + }; + if (typeof require == 'function') { + if (typeof scope.URL != 'undefined') { + // node + scope.URL = require('url').URL; + } + if (typeof scope.URLSearchParams == 'undefined') { + // node + scope.URLSearchParams = require('url-search-params-polyfill'); + } + } + scope.deepClone = (from, allowNonSerializable) => { + if (from == null || typeof from != "object") return from; + if (from.constructor != Object && from.constructor != Array) return from; + if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function || from.constructor == String || from.constructor == Number || from.constructor == Boolean) return new from.constructor(from); + let to = new from.constructor(); + for (var name in from) { + if (allowNonSerializable || ['string', 'object', 'number', 'boolean'].includes(typeof from[name])) { + to[name] = typeof to[name] == "undefined" ? scope.deepClone(from[name], allowNonSerializable) : to[name]; + } + } + return to; + }; + scope.kfmt = (num, digits) => { + var si = [{ + value: 1, + symbol: "" + }, { + value: 1E3, + symbol: "K" + }, { + value: 1E6, + symbol: "M" + }, { + value: 1E9, + symbol: "G" + }, { + value: 1E12, + symbol: "T" + }, { + value: 1E15, + symbol: "P" + }, { + value: 1E18, + symbol: "E" + }]; + var i, + rx = /\.0+$|(\.[0-9]*[1-9])0+$/; + for (i = si.length - 1; i > 0; i--) { + if (num >= si[i].value) { + break; + } + } + return (num / si[i].value).toFixed(digits).replace(rx, "$1") + si[i].symbol; + }; + scope.kbfmt = (bytes, decimals = 2) => { + // https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript + if (isNaN(bytes) || typeof bytes != 'number') return 'N/A'; + if (bytes === 0) return '0 Bytes'; + const k = 1024, + dm = decimals < 0 ? 0 : decimals, + sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + }; + scope.kbsfmt = (bytes, decimals = 1) => { + // https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript + if (isNaN(bytes) || typeof bytes != 'number') return 'N/A'; + if (bytes === 0) return '0 Bytes/ps'; + const k = 1024, + dm = decimals < 0 ? 0 : decimals, + sizes = ['Bytes/ps', 'KBps', 'MBps', 'GBps', 'TBps', 'PBps', 'EBps', 'ZBps', 'YBps']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + }; + scope.componentToHex = c => { + var hex = c.toString(16); + return hex.length == 1 ? '0' + hex : hex; + }; + scope.rgbToHex = (r, g, b) => { + return '#' + scope.componentToHex(r) + scope.componentToHex(g) + scope.componentToHex(b); + }; + scope.hexToRgb = ohex => { + var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i, + hex = ohex.replace(shorthandRegex, (m, r, g, b) => { + return r + r + g + g + b + b; + }); + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : ohex; + }; + scope.ucWords = (str, force) => { + if (!force && str != str.toLowerCase()) { + return str; + } + return str.replace(new RegExp('(^|[ ])[A-zÀ-ú]', 'g'), letra => { + return letra.toUpperCase(); + }); + }; + scope.ucFirst = (str, keepCase) => { + if (!keepCase) { + str = str.toLowerCase(); + } + return str.replace(/^[\u00C0-\u1FFF\u2C00-\uD7FF\w]/g, letter => { + return letter.toUpperCase(); + }); + }; + scope.getArrayMax = arr => { + // https://stackoverflow.com/questions/42623071/maximum-call-stack-size-exceeded-with-math-min-and-math-max + let len = arr.length; + let max = -Infinity; + while (len--) { + if (arr[len] > max) max = arr[len]; + } + return max; + }; + scope.getArrayMin = arr => { + let len = arr.length; + let min = Infinity; + while (len--) { + if (arr[len] < min) min = arr[len]; + } + return max; + }; + scope.hmsClockToSeconds = str => { + var cs = str.split('.'), + p = cs[0].split(':'), + s = 0, + m = 1; + while (p.length > 0) { + s += m * parseInt(p.pop(), 10); + m *= 60; + } + if (cs.length > 1 && cs[1].length >= 2) { + s += parseInt(cs[1].substr(0, 2)) / 100; + } + return s; + }; + scope.hmsSecondsToClock = secs => { + var sec_num = parseInt(secs, 10); // don't forget the second param + var hours = Math.floor(sec_num / 3600); + var minutes = Math.floor((sec_num - hours * 3600) / 60); + var seconds = sec_num - hours * 3600 - minutes * 60; + if (hours < 10) { + hours = "0" + hours; + } + if (minutes < 10) { + minutes = "0" + minutes; + } + if (seconds < 10) { + seconds = "0" + seconds; + } + return hours + ':' + minutes + ':' + seconds; + }; + scope.ts2clock = time => { + let locale = undefined, + timezone = undefined; + if (typeof time == 'string') { + time = parseInt(time); + } + time = global.moment(time * 1000); + return time.format('LT'); + }; + scope.getUniqueFilenameHelper = (name, i) => { + let pos = name.lastIndexOf('.'); + if (pos == -1) { + return name + '-' + i; + } else { + return name.substr(0, pos) + '-' + i + name.substr(pos); + } + }; + scope.getUniqueFilename = (files, name) => { + let i = 0, + nname = name; + while (files.includes(nname)) { + i++; + nname = scope.getUniqueFilenameHelper(name, i); + } + return nname; + }; + scope.traceback = () => { + try { + var a = {}; + a.debug(); + } catch (ex) { + return ex.stack.replace('TypeError: a.debug is not a function', '').trim(); + } + }; + scope.forwardSlashes = path => { + if (path && path.indexOf('\\') != -1) { + return path.replaceAll('\\', '/').replaceAll('//', '/'); + } + return path; + }; + scope.absolutize = (path, url) => { + if (!path) return url; + if (!url) return path; + if (path.startsWith('//')) { + path = 'http:' + path; + } + if (path.match(new RegExp('^[htps:]?//'))) { + return path; + } + let uri; + try { + uri = new URL(path, url); + return uri.href; + } catch (e) { + return global.joinPath(url, path); + } + }; + scope.joinPath = (folder, file) => { + if (!file) return folder; + if (!folder) return file; + let ffolder = folder; + let ffile = file; + if (ffolder.indexOf('\\') != -1) { + ffolder = scope.forwardSlashes(ffolder); + } + if (ffile.indexOf('\\') != -1) { + ffile = scope.forwardSlashes(ffile); + } + let folderEndsWithSlash = ffolder.charAt(ffolder.length - 1) == '/'; + let fileStartsWithSlash = ffile.charAt(0) == '/'; + if (fileStartsWithSlash && folderEndsWithSlash) { + ret = ffolder + ffile.substr(1); + } else if (fileStartsWithSlash || folderEndsWithSlash) { + ret = ffolder + ffile; + } else { + ret = ffolder + '/' + ffile; + } + return ret; + }; + scope.time = () => { + return Date.now() / 1000; + }; + scope.isVODM3U8 = (content, contentLength) => { + let sample = String(content).toLowerCase(); + if (sample.indexOf('#ext-x-playlist-type:vod') != -1) return true; + if (sample.match(new RegExp('#ext-x-media-sequence:0[^0-9]'))) return true; + let pe = sample.indexOf('#ext-x-endlist'); + let px = sample.lastIndexOf('#extinf'); + if (pe != -1) { + return pe > px; + } + if (sample.indexOf('#ext-x-program-date-time') == -1) { + let pieces = sample.split('#extinf'); + if (pieces.length > 30) { + return true; + } + if (typeof contentLength == 'number' && pieces.length > 2) { + // at least 3 pieces, to ensure that the first extinf is complete + let header = pieces.shift(); + let pieceLen = pieces[0].length + 7; + let totalEstimatedPieces = (contentLength - header.length) / pieceLen; + if (totalEstimatedPieces > 30) { + return true; + } + } + } + }; + scope.filenameFromURL = (url, defaultExt = 'mp4') => { + let filename = url.split('?')[0].split('/').filter(s => s).pop(); + if (!filename || filename.indexOf('=') != -1) { + filename = 'video'; + } + if (filename.indexOf('.') == -1) { + filename += '.' + defaultExt; + } + return scope.sanitize(filename); + }; + scope.isWritable = stream => { + return (stream.writable || stream.writeable) && !stream.finished; + }; + scope.checkDirWritePermission = async dir => { + const file = dir + '/temp.txt', + fsp = scope.getFS().promises; + await fsp.writeFile(file, '0'); + await fsp.unlink(file); + return true; + }; + scope.checkDirWritePermissionSync = dir => { + let fine; + const file = dir + '/temp.txt', + fs = scope.getFS(); + try { + fs.writeFileSync(file, '0'); + fine = true; + fs.unlinkSync(file); + } catch (e) { + console.error(e); + } + return fine; + }; + scope.rmdir = (folder, itself, cb) => { + const rimraf = require('rimraf'); + let dir = folder; + if (dir.charAt(dir.length - 1) == '/') { + dir = dir.substr(0, dir.length - 1); + } + if (!itself) { + dir += '/*'; + } + if (cb === true) { + // sync + try { + rimraf.sync(dir); + } catch (e) {} + } else { + try { + rimraf(dir, cb || (() => {})); + } catch (e) { + if (typeof cb == 'function') { + cb(); + } + } + } + }; + scope.dirname = _path => { + let parts = _path.replace(new RegExp('\\\\', 'g'), '/').split('/'); + parts.pop(); + return parts.join('/'); + }; + scope.getFS = () => { + if (!scope.__fs) { + scope.__fs = require('fs'); + } + return scope.__fs; + }; + scope._moveFile = async (from, to) => { + const fs = scope.getFS(); + const fstat = await fs.promises.stat(from).catch(console.error); + if (!fstat) throw '"from" file not found'; + let err; + await fs.promises.copyFile(from, to).catch(e => err = e); + let tstat; + if (typeof err != 'undefined') { + tstat = await fs.promises.stat(to).catch(console.error); + if (tstat && tstat.size == fstat.size) { + err = null; + } + } + if (err) { + throw err; + } + fs.promises.unlink(from).catch(() => {}); + return true; + }; + scope.moveFile = (from, to, _cb, timeout = 5, until = null, startedAt = null, fromSize = null) => { + const fs = scope.getFS(), + now = scope.time(), + cb = () => { + if (_cb) { + _cb(); + _cb = null; + } + }; + if (until === null) { + until = now + timeout; + } + if (startedAt === null) { + startedAt = now; + } + const move = () => { + scope._moveFile(from, to).then(() => cb()).catch(err => { + if (until <= now) { + fs.access(from, (err, stat) => { + console.error('MOVERETRY GAVEUP AFTER ' + (now - startedAt) + ' SECONDS', err, fromSize, err); + return cb(err); + }); + return; + } + fs.stat(to, (ferr, stat) => { + if (stat && stat.size == fromSize) { + cb(); + } else { + fs.stat(from, (err, stat) => { + if (stat && stat.size == fromSize) { + setTimeout(() => { + scope.moveFile(from, to, cb, timeout, until, startedAt, fromSize); + }, 500); + } else { + console.error('MOVERETRY FROM FILE WHICH DOESNT EXISTS ANYMORE', err, stat); + console.error(ferr, err); + cb(err || '"from" file changed'); + } + }); + } + }); + }); + }; + if (fromSize === null) { + fs.stat(from, (err, stat) => { + if (err) { + console.error('MOVERETRY FROM FILE NEVER EXISTED', err); + cb(err); + } else { + fromSize = stat.size; + move(); + } + }); + } else { + move(); + } + }; + scope.execSync = cmd => { + let stdout; + try { + stdout = require('child_process').execSync(cmd); + } catch (e) { + stdout = String(e); + } + return String(stdout); + }; + scope.isNetworkIP = addr => { + if (addr) { + if (addr.startsWith('10.') || addr.startsWith('172.') || addr.startsWith('192.')) { + return 'ipv4'; + } + } + }; + scope.androidSDKVer = () => { + if (!scope.androidSDKVerCache) { + scope.androidSDKVerCache = parseInt(scope.execSync('getprop ro.build.version.sdk').trim()); + } + return scope.androidSDKVerCache; + }; + scope.os = () => { + if (!scope.osCache) { + scope.osCache = require('os'); + } + return scope.osCache; + }; + scope.networkIpCache = false; + scope.networkIpCacheTTL = 10; + scope.networkDummyInterfaces = addr => { + return { + "Wi-Fi": [{ + "address": addr, + "netmask": "255.255.255.0", + "family": "IPv4", + "mac": "00:00:00:00:00:00", + "internal": false + }], + "Loopback Pseudo-Interface 1": [{ + "address": "127.0.0.1", + "netmask": "255.0.0.0", + "family": "IPv4", + "mac": "00:00:00:00:00:00", + "internal": true, + "cidr": "127.0.0.1/8" + }] + }; + }; + scope.androidIPCommand = () => { + return scope.execSync('ip route get 8.8.8.8'); + }; + scope.networkInterfaces = () => { + if (process.platform == 'android') { + let sdkVer = scope.androidSDKVer(); + if (isNaN(sdkVer) || sdkVer < 20 || sdkVer >= 29) { + // keep "sdkVer < x" check + // on most recent sdks, os.networkInterces() crashes nodejs-mobile-cordova with a uv_interface_addresses error + let addr, + time = scope.time(); + if (scope.networkIpCache && scope.networkIpCache.time + scope.networkIpCacheTTL > time) { + addr = scope.networkIpCache.addr; + } else { + addr = scope.androidIPCommand().match(new RegExp('src +([0-9\.]+)')); + if (addr) { + addr = addr[1]; + scope.networkIpCache = { + addr, + time + }; + } else { + addr = scope.networkIpCache ? scope.networkIpCache.addr : '127.0.0.1'; + } + } + return scope.networkDummyInterfaces(addr); + } + } + return scope.os().networkInterfaces(); + }; + scope.networkIP = () => { + let interfaces = scope.networkInterfaces(), + addr = '127.0.0.1', + skipIfs = new RegExp('(vmware|virtualbox)', 'i'); + for (let devName in interfaces) { + if (devName.match(skipIfs)) continue; + let iface = interfaces[devName]; + for (let i = 0; i < iface.length; i++) { + let alias = iface[i]; + if (alias.family === 'IPv4' && !alias.internal && scope.isNetworkIP(alias.address)) { + addr = alias.address; + } + } + } + return addr; + }; + scope.parseJSON = json => { + // prevent JSON related crashes + let ret; + try { + let parsed = JSON.parse(json); + ret = parsed; + } catch (e) {} + return ret; + }; } - -if(typeof(module) != 'undefined' && typeof(module.exports) != 'undefined'){ - module.exports = patch +if (typeof module != 'undefined' && typeof module.exports != 'undefined') { + module.exports = patch; } else { - patch(window) + patch(window); } - - diff --git a/www/nodejs-project/modules/theme/client.js b/www/nodejs-project/modules/theme/client.js index f08b0d02..2da13d84 100644 --- a/www/nodejs-project/modules/theme/client.js +++ b/www/nodejs-project/modules/theme/client.js @@ -8,9 +8,10 @@ if(typeof(themeRefresh) == 'undefined'){ var r = colorChannelMixer(rgbA[0], rgbB[0], amountToMix) var g = colorChannelMixer(rgbA[1], rgbB[1], amountToMix) var b = colorChannelMixer(rgbA[2], rgbB[2], amountToMix) - return "rgb("+r+","+g+","+b+")" + return "rgb("+ [r, g, b].join(", ") +")" } function themeRefresh(){ + if(!window.hexToRGBA) return const systemFont = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif' let family = config['font-family'], nfs = 0.0275 + (config['font-size'] * 0.0015) if(!family){ diff --git a/www/nodejs-project/package.json b/www/nodejs-project/package.json index f8f05580..463e015c 100644 --- a/www/nodejs-project/package.json +++ b/www/nodejs-project/package.json @@ -45,7 +45,7 @@ "description": "A intuitive, multi-language and cross-platform IPTV player.", "name": "megacubo", "icon": "./default_icon.png", - "version": "17.1.5", + "version": "17.1.6", "theme": { "fullScreen": true }, @@ -56,7 +56,6 @@ "https://*/" ], "main": "main.js", - "chromium-args": "--enable-node-worker --enable-features=PlatformHEVCDecoderSupport --experimental-worker --ignore-urlfetcher-cert-requests --tls13-variant=disabled --disable-web-security --allow-hidden-media-playback --disable-background-timer-throttling --proxy-auto-detect --no-zygote --no-sandbox --enable-gpu-async-worker-context --password-store=basic --force-device-scale-factor=1 --process-per-tab --enable-smooth-scrolling --disable-crash-handler=true --disable-breakpad --no-experiments --no-pings --disable-transparency --enable-experimental-web-platform-features --remote-debugging-port=9229 --disable-features=IsolateOrigins,site-per-process,nw2,NetworkPrediction", "js-flags": "--max_old_space_size=2048", "webkit": { "page-cache": true