From 10db2b285ce3ae89b2ba21f75d3a40dd077114f9 Mon Sep 17 00:00:00 2001 From: Efox Date: Thu, 27 Jul 2023 21:56:44 -0300 Subject: [PATCH] v17.0.8 - Bugfix on mute feature, it was unmuting on playback recovering - Fixed a bug which could lead to tuning freeze - Relevant performance improvements --- config.xml | 2 +- package.json | 2 +- www/nodejs-project/assets/js/index/video.js | 9 +- www/nodejs-project/lang/ar.json | 1 + www/nodejs-project/lang/de.json | 1 + www/nodejs-project/lang/el.json | 1 + www/nodejs-project/lang/en.json | 1 + www/nodejs-project/lang/es.json | 1 + www/nodejs-project/lang/fr.json | 1 + www/nodejs-project/lang/hi.json | 1 + www/nodejs-project/lang/it.json | 1 + www/nodejs-project/lang/pl.json | 1 + www/nodejs-project/lang/pt.json | 1 + www/nodejs-project/lang/ru.json | 1 + www/nodejs-project/lang/sq.json | 1 + www/nodejs-project/lang/tr.json | 1 + www/nodejs-project/lang/zh.json | 1 + .../modules/channels/channels.js | 8 +- .../modules/download/download-cache.js | 2 +- www/nodejs-project/modules/lists/index.js | 63 +++++++++--- .../modules/lists/list-index-utils.js | 23 ++++- www/nodejs-project/modules/lists/lists.js | 2 +- www/nodejs-project/modules/lists/loader.js | 7 +- www/nodejs-project/modules/lists/manager.js | 97 ++++++++++++++----- www/nodejs-project/modules/lists/parser.js | 22 ++--- .../modules/lists/update-list-index.js | 9 +- www/nodejs-project/modules/storage/storage.js | 2 +- www/nodejs-project/modules/streamer/client.js | 13 ++- .../modules/streamer/streamer.js | 4 +- .../modules/supercharge/supercharge.js | 9 +- www/nodejs-project/modules/tuner/tuner.js | 17 ++-- www/nodejs-project/package.json | 2 +- 32 files changed, 216 insertions(+), 91 deletions(-) diff --git a/config.xml b/config.xml index 7dbd3a70..2c0a6143 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 54cf89cf..68ab3285 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tv.megacubo.app", "displayName": "Megacubo", - "version": "17.0.7", + "version": "17.0.8", "description": "A intuitive and multi-language IPTV player.", "main": "index.js", "scripts": { diff --git a/www/nodejs-project/assets/js/index/video.js b/www/nodejs-project/assets/js/index/video.js index 45295998..3d502495 100644 --- a/www/nodejs-project/assets/js/index/video.js +++ b/www/nodejs-project/assets/js/index/video.js @@ -114,7 +114,9 @@ class VideoControl extends EventEmitter { } const useCurtains = config['fx-nav-intensity'] this.rootElement.removeClass('playing') - if(!this.uiFrame) this.uiFrame = jQuery(document.querySelector('iframe').contentWindow.document.body) + if(!this.uiFrame) { + this.uiFrame = jQuery(document.querySelector('iframe').contentWindow.document.body) + } this.uiFrame.removeClass('video video-loading video-playing video-paused') if(useCurtains){ this.rootElement.addClass('curtains-static').removeClass('curtains-alpha').removeClass('curtains').removeClass('curtains-close') @@ -143,6 +145,11 @@ class VideoControl extends EventEmitter { this.current.volume(l) } } + muted(){ + if(this.current){ + return this.current.object.muted + } + } setState(s, err){ if(this.state != s){ this.state = s diff --git a/www/nodejs-project/lang/ar.json b/www/nodejs-project/lang/ar.json index cfd2559e..30eb3958 100644 --- a/www/nodejs-project/lang/ar.json +++ b/www/nodejs-project/lang/ar.json @@ -118,6 +118,7 @@ "CONNECTING": "جاري الاتصال", "CONNECTION_FAILURE": "فشل الاتصال", "CONNECT_TIMEOUT": "انتهاء مهلة الاصتال", + "CONTACT_PROVIDER": "اتصل بمزودك", "CONTINUE": "استمر", "CONTROL_PLAYBACK_RATE": "التحكم بمعدل تشغيل وفقًا للمخزن المؤقت", "CONVERTING": "التحويل...", diff --git a/www/nodejs-project/lang/de.json b/www/nodejs-project/lang/de.json index a32bf909..bcbaac5b 100644 --- a/www/nodejs-project/lang/de.json +++ b/www/nodejs-project/lang/de.json @@ -118,6 +118,7 @@ "CONNECTING": "Anschluss", "CONNECTION_FAILURE": "Verbindung fehlschlagen", "CONNECT_TIMEOUT": "Verbindungs Timeout", + "CONTACT_PROVIDER": "Wenden Sie sich an Ihren Anbieter", "CONTINUE": "Fortsetzen", "CONTROL_PLAYBACK_RATE": "Steuerungsreproduktionsrate gemäß Puffer", "CONVERTING": "Konvertieren...", diff --git a/www/nodejs-project/lang/el.json b/www/nodejs-project/lang/el.json index 524512d1..5e0158ea 100644 --- a/www/nodejs-project/lang/el.json +++ b/www/nodejs-project/lang/el.json @@ -118,6 +118,7 @@ "CONNECTING": "Συνδετικός", "CONNECTION_FAILURE": "Η σύνδεση αποτυγχάνει", "CONNECT_TIMEOUT": "Συνδέστε το χρονικό όριο", + "CONTACT_PROVIDER": "Επικοινωνήστε με τον παροχέα σας", "CONTINUE": "Να συνεχίσει", "CONTROL_PLAYBACK_RATE": "Ποσοστό αναπαραγωγής ελέγχου σύμφωνα με το buffer", "CONVERTING": "Μετατροπή...", diff --git a/www/nodejs-project/lang/en.json b/www/nodejs-project/lang/en.json index a01a6467..21dd0c92 100644 --- a/www/nodejs-project/lang/en.json +++ b/www/nodejs-project/lang/en.json @@ -118,6 +118,7 @@ "CONNECTING": "Connecting", "CONNECTION_FAILURE": "Connection fail", "CONNECT_TIMEOUT": "Connect timeout", + "CONTACT_PROVIDER": "Contact your provider", "CONTINUE": "Continue", "CONTROL_PLAYBACK_RATE": "Control reproduction rate according to buffer", "CONVERTING": "Converting...", diff --git a/www/nodejs-project/lang/es.json b/www/nodejs-project/lang/es.json index db3db84c..f83f1726 100644 --- a/www/nodejs-project/lang/es.json +++ b/www/nodejs-project/lang/es.json @@ -118,6 +118,7 @@ "CONNECTING": "Conectando", "CONNECTION_FAILURE": "Conexión fallida", "CONNECT_TIMEOUT": "Tiempo de espera para conexión", + "CONTACT_PROVIDER": "Póngase en contacto con su proveedor", "CONTINUE": "Continuar", "CONTROL_PLAYBACK_RATE": "Tasa de reproducción de control según el búfer", "CONVERTING": "Convertir...", diff --git a/www/nodejs-project/lang/fr.json b/www/nodejs-project/lang/fr.json index 60be7c26..d4697411 100644 --- a/www/nodejs-project/lang/fr.json +++ b/www/nodejs-project/lang/fr.json @@ -118,6 +118,7 @@ "CONNECTING": "De liaison", "CONNECTION_FAILURE": "Échec de la connexion", "CONNECT_TIMEOUT": "Connecter le délai d'attente", + "CONTACT_PROVIDER": "Contactez votre fournisseur", "CONTINUE": "Continuez", "CONTROL_PLAYBACK_RATE": "Taux de reproduction de contrôle en fonction de la mémoire tampon", "CONVERTING": "Conversion...", diff --git a/www/nodejs-project/lang/hi.json b/www/nodejs-project/lang/hi.json index 3116e1b8..c9be4ce8 100644 --- a/www/nodejs-project/lang/hi.json +++ b/www/nodejs-project/lang/hi.json @@ -118,6 +118,7 @@ "CONNECTING": "कनेक्ट", "CONNECTION_FAILURE": "संबंध विफल", "CONNECT_TIMEOUT": "टाइमआउट कनेक्ट करें", + "CONTACT_PROVIDER": "अपने प्रदाता से संपर्क करें", "CONTINUE": "जारी रखें", "CONTROL_PLAYBACK_RATE": "बफर के अनुसार नियंत्रण प्रजनन दर", "CONVERTING": "परिवर्तित...", diff --git a/www/nodejs-project/lang/it.json b/www/nodejs-project/lang/it.json index 16726816..d901be0e 100644 --- a/www/nodejs-project/lang/it.json +++ b/www/nodejs-project/lang/it.json @@ -118,6 +118,7 @@ "CONNECTING": "Connessione in corso", "CONNECTION_FAILURE": "Connessione fallimento", "CONNECT_TIMEOUT": "Timeout connessione", + "CONTACT_PROVIDER": "Contatta il tuo provider", "CONTINUE": "Continua", "CONTROL_PLAYBACK_RATE": "Controllare la velocità di riproduzione in base al buffer", "CONVERTING": "Convertire...", diff --git a/www/nodejs-project/lang/pl.json b/www/nodejs-project/lang/pl.json index 74b22114..b6bac63e 100644 --- a/www/nodejs-project/lang/pl.json +++ b/www/nodejs-project/lang/pl.json @@ -118,6 +118,7 @@ "CONNECTING": "Złączony", "CONNECTION_FAILURE": "Połączenie awarii", "CONNECT_TIMEOUT": "Connect timeout", + "CONTACT_PROVIDER": "Skontaktuj się z dostawcą", "CONTINUE": "Kontyntynuj", "CONTROL_PLAYBACK_RATE": "Sterowanie reprodukcją w zależności od bufora", "CONVERTING": "Przekształcenie...", diff --git a/www/nodejs-project/lang/pt.json b/www/nodejs-project/lang/pt.json index 81759c2e..f87ad197 100644 --- a/www/nodejs-project/lang/pt.json +++ b/www/nodejs-project/lang/pt.json @@ -118,6 +118,7 @@ "CONNECTING": "Conectando", "CONNECTION_FAILURE": "Falha na conexão", "CONNECT_TIMEOUT": "Tempo limite para conexão", + "CONTACT_PROVIDER": "Contate seu provedor", "CONTINUE": "Continue", "CONTROL_PLAYBACK_RATE": "Controlar taxa de reprodução de acordo com o buffer", "CONVERTING": "Convertendo...", diff --git a/www/nodejs-project/lang/ru.json b/www/nodejs-project/lang/ru.json index 01cab80a..7b8cb34b 100644 --- a/www/nodejs-project/lang/ru.json +++ b/www/nodejs-project/lang/ru.json @@ -118,6 +118,7 @@ "CONNECTING": "Соединение", "CONNECTION_FAILURE": "Сбой соединения", "CONNECT_TIMEOUT": "Подключите тайм-аут", + "CONTACT_PROVIDER": "Свяжитесь с вашим провайдером", "CONTINUE": "Продолжать", "CONTROL_PLAYBACK_RATE": "Уровень воспроизведения контроля в соответствии с буфером", "CONVERTING": "Конвертирование...", diff --git a/www/nodejs-project/lang/sq.json b/www/nodejs-project/lang/sq.json index ccd51260..1a3fb1f2 100644 --- a/www/nodejs-project/lang/sq.json +++ b/www/nodejs-project/lang/sq.json @@ -118,6 +118,7 @@ "CONNECTING": "Lidh", "CONNECTION_FAILURE": "Lidhja dështon", "CONNECT_TIMEOUT": "Lidhni timout", + "CONTACT_PROVIDER": "Kontaktoni me ofruesin tuaj", "CONTINUE": "Vazhdoj", "CONTROL_PLAYBACK_RATE": "Norma e riprodhimit të kontrollit sipas tamponit", "CONVERTING": "Duke u kthyer...", diff --git a/www/nodejs-project/lang/tr.json b/www/nodejs-project/lang/tr.json index 3f5be06a..b1ac308b 100644 --- a/www/nodejs-project/lang/tr.json +++ b/www/nodejs-project/lang/tr.json @@ -118,6 +118,7 @@ "CONNECTING": "Bağlanıyor", "CONNECTION_FAILURE": "Bağlantı başarısız", "CONNECT_TIMEOUT": "Bağlantı zaman aşımı", + "CONTACT_PROVIDER": "Sağlayıcınızla iletişime geçin", "CONTINUE": "Devam et", "CONTROL_PLAYBACK_RATE": "Tampona göre reprodüksiyon oranını kontrol edin", "CONVERTING": "Dönüştürme...", diff --git a/www/nodejs-project/lang/zh.json b/www/nodejs-project/lang/zh.json index abddbb20..a83c04bd 100644 --- a/www/nodejs-project/lang/zh.json +++ b/www/nodejs-project/lang/zh.json @@ -118,6 +118,7 @@ "CONNECTING": "连接", "CONNECTION_FAILURE": "连接失败", "CONNECT_TIMEOUT": "限制时间连接", + "CONTACT_PROVIDER": "联系您的提供商", "CONTINUE": "继续", "CONTROL_PLAYBACK_RATE": "根据缓冲区控制播放率", "CONVERTING": "转换...", diff --git a/www/nodejs-project/modules/channels/channels.js b/www/nodejs-project/modules/channels/channels.js index 3b67c882..87bfd711 100644 --- a/www/nodejs-project/modules/channels/channels.js +++ b/www/nodejs-project/modules/channels/channels.js @@ -294,11 +294,15 @@ class ChannelsEPG extends ChannelsCategories { }, async data => { const name = data.originalName || data.name const category = this.getChannelCategory(name) - if(category){ + if(!global.activeEPG) { + global.displayErr(global.lang.EPG_DISABLED) + } else if(!this.loadedEPG) { + global.displayErr(global.lang.EPG_AVAILABLE_SOON) + } else if(category) { let err await this.epgChannelLiveNow(data).catch(e => { err = e - global.displayErr(global.lang.CHANNEL_EPG_NOT_FOUND) + global.displayErr(global.lang.CHANNEL_EPG_NOT_FOUND +' *') }) if(!err) { const _path = [global.lang.LIVE, category, name, global.lang.EPG].join('/') diff --git a/www/nodejs-project/modules/download/download-cache.js b/www/nodejs-project/modules/download/download-cache.js index 0cceed4c..d24b7dcb 100644 --- a/www/nodejs-project/modules/download/download-cache.js +++ b/www/nodejs-project/modules/download/download-cache.js @@ -21,7 +21,7 @@ class DownloadCacheFileReader extends Events { } destroy(){ this.removeAllListeners() - this.stream && this.stream.destroy() + this.stream && this.stream.close() } } diff --git a/www/nodejs-project/modules/lists/index.js b/www/nodejs-project/modules/lists/index.js index f3ac649c..c3eb4922 100644 --- a/www/nodejs-project/modules/lists/index.js +++ b/www/nodejs-project/modules/lists/index.js @@ -346,26 +346,35 @@ class Index extends Common { } intersectMap(a, b){ let c = {} - Object.keys(b).forEach(listUrl => { + for(const listUrl in b) { if(typeof(a[listUrl]) != 'undefined'){ + const gset = new Set( + a[listUrl].g.length && b[listUrl].g.length ? + b[listUrl].g : [] + ) + const nset = new Set( + a[listUrl].n.length && b[listUrl].n.length ? + b[listUrl].n : [] + ) c[listUrl] = { - g: a[listUrl].g ? a[listUrl].g.filter(n => b[listUrl].g.includes(n)).sort((a, b) => a - b) : [], - n: a[listUrl].n ? a[listUrl].n.filter(n => b[listUrl].n.includes(n)).sort((a, b) => a - b) : [] + g: gset.size ? a[listUrl].g.filter(n => gset.has(n)) : [], + n: nset.size ? a[listUrl].n.filter(n => nset.has(n)) : [] } } - }) + } return c } joinMap(a, b){ let c = this.cloneMap(a) // clone it - Object.keys(b).forEach(listUrl => { + for(const listUrl in b) { if(typeof(c[listUrl]) == 'undefined'){ c[listUrl] = {g: [], n: []} } - Object.keys(b[listUrl]).forEach(type => { + for(const type in b[listUrl]) { let changed + const map = new Set(c[listUrl][type] || []) b[listUrl][type].forEach(n => { - if(!c[listUrl][type].includes(n)){ + if(!map.has(n)){ c[listUrl][type].push(n) if(!changed){ changed = true @@ -375,29 +384,51 @@ class Index extends Common { if(changed){ c[listUrl][type].sort((a, b) => a - b) } - }) - }) + } + } return c } - diffMap(a, b){ + _diffMap(a, b){ let c - Object.keys(b).forEach(listUrl => { - if(typeof(a[listUrl]) != 'undefined'){ - Object.keys(b[listUrl]).forEach(type => { + for(const listUrl in b) { + if(typeof(a[listUrl]) != 'undefined') { + for(const type in b[listUrl]) { if(typeof(a[listUrl][type]) != 'undefined'){ + const map = new Set(c ? c[listUrl][type] : a[listUrl][type]) b[listUrl][type].forEach(n => { let i = c ? c[listUrl][type].indexOf(n) : a[listUrl][type].indexOf(n) - if(i != -1){ + if(map.has(n)){ if(!c) c = this.cloneMap(a) // clone it lazily c[listUrl][type].splice(i, 1) } }) } - }) + } } - }) + } return c || a } + diffMap(a, b) { + let c = {} + for (const listUrl in b) { + if (a[listUrl] !== undefined) { + c[listUrl] = {g: [], n: []} + const gSet = new Set(a[listUrl].g) + const nSet = new Set(a[listUrl].n) + for (const type in b[listUrl]) { + if (a[listUrl][type] !== undefined) { + const diffSet = new Set(b[listUrl][type]) + if (type === 'g') { + c[listUrl].g = [...gSet].filter(n => !diffSet.has(n)) + } else { + c[listUrl].n = [...nSet].filter(n => !diffSet.has(n)) + } + } + } + } + } + return c + } cloneMap(a){ return global.deepClone(a) } diff --git a/www/nodejs-project/modules/lists/list-index-utils.js b/www/nodejs-project/modules/lists/list-index-utils.js index 9629acb6..eb0885cc 100644 --- a/www/nodejs-project/modules/lists/list-index-utils.js +++ b/www/nodejs-project/modules/lists/list-index-utils.js @@ -115,7 +115,7 @@ class ListIndexUtils extends Events { }) } async readLastLine() { - const bufferSize = 2048 + const bufferSize = 16834 const { size } = await fs.promises.stat(this.file) const fd = await fs.promises.open(this.file, 'r') let line = '' @@ -143,14 +143,29 @@ class ListIndexUtils extends Events { let index = false if(line){ try { - index = global.parseJSON(line) + let parsed = global.parseJSON(line) + if(Array.isArray(parsed)) { + this.linesMap = parsed + const fd = await fs.promises.open(this.file, 'r') + const from = parsed[parsed.length - 2] + const length = parsed[parsed.length - 1] - from + const buffer = Buffer.alloc(length) + const { bytesRead } = await fd.read(buffer, 0, length, from) + await fd.close().catch(console.error) + line = String(buffer).substr(0, bytesRead) + index = JSON.parse(line) + } else { + index = parsed // old style compat + } } catch(e) { console.error('Index parsing failure', line, e, this.file) } } if(index && typeof(index.length) != 'undefined'){ - this.linesMap = index.linesMap - delete index.linesMap + if(index.linesMap) { + this.linesMap = index.linesMap + delete index.linesMap + } return index } else { console.error('Bad index', String(line).substr(0, 256), this.file) diff --git a/www/nodejs-project/modules/lists/lists.js b/www/nodejs-project/modules/lists/lists.js index b46fc894..2207adc9 100644 --- a/www/nodejs-project/modules/lists/lists.js +++ b/www/nodejs-project/modules/lists/lists.js @@ -585,7 +585,7 @@ class Lists extends ListsEPGTools { }) return dup } - isExpiredList(list){ + isExpiredList(list){ // check if links are all pointing to some few URLs if(!list || !list.index || this.myLists.includes(list.url)){ return } diff --git a/www/nodejs-project/modules/lists/loader.js b/www/nodejs-project/modules/lists/loader.js index 50243383..cd718166 100644 --- a/www/nodejs-project/modules/lists/loader.js +++ b/www/nodejs-project/modules/lists/loader.js @@ -158,10 +158,10 @@ class ListsLoader extends Events { if(p.progressId == uid) progress(p.progress) }) await this.updater.update(url, false, uid).catch(console.error) + this.updater && this.updater.close() this.master.addList(url, 1) - this.updater.close() } - schedule(url, priority){ + schedule(url, priority){ let cancel, started, done this.processes.some(p => p.url == url) || this.processes.push({ promise: this.queue.add(async () => { @@ -171,10 +171,10 @@ class ListsLoader extends Events { await this.prepareUpdater() this.results[url] = 'awaiting' this.results[url] = await this.updater.update(url).catch(console.error) + this.updater && this.updater.close() done = true const add = this.results[url] == 'updated' || (this.results[url] == 'already updated' && !this.master.processedLists.has(url)) add && this.master.addList(url, priority) - this.updater.close() }, { priority }), started: () => { return started @@ -190,6 +190,7 @@ class ListsLoader extends Events { await this.prepareUpdater() this.results[url] = 'reloading' this.results[url] = await this.updater.updateList(url, true).catch(err => updateErr = err) + this.updater && this.updater.close() if(updateErr) throw updateErr await this.master.loadList(url).catch(err => updateErr = err) if(updateErr) throw updateErr diff --git a/www/nodejs-project/modules/lists/manager.js b/www/nodejs-project/modules/lists/manager.js index be909187..35c9617b 100644 --- a/www/nodejs-project/modules/lists/manager.js +++ b/www/nodejs-project/modules/lists/manager.js @@ -325,8 +325,8 @@ class ManagerEPG extends ManagerCommunityLists { return details } updateEPGStatus(){ - let p = global.explorer.path - if(p.indexOf(global.lang.EPG) == -1){ + const p = global.explorer.path + if(p.indexOf(global.lang.EPG +'/'+ global.lang.OPTIONS) == -1) { clearInterval(this.epgStatusTimer) this.epgStatusTimer = false } else { @@ -407,6 +407,7 @@ class ManagerEPG extends ManagerCommunityLists { }}) resolve(options) } + this.lastActiveEPGDetails = '' if(activeEPG){ const epgNext = () => { if(activeEPGDetails == global.lang.ENABLED){ @@ -415,9 +416,8 @@ class ManagerEPG extends ManagerCommunityLists { this.epgStatusTimer = false } } else { - if(!this.epgStatusTimer){ - this.epgStatusTimer = setInterval(this.updateEPGStatus.bind(this), 1000) - } + this.epgStatusTimer && clearInterval(this.epgStatusTimer) + this.epgStatusTimer = setInterval(this.updateEPGStatus.bind(this), 1000) } next() } @@ -426,12 +426,10 @@ class ManagerEPG extends ManagerCommunityLists { } else { this.master.epg([], 2).then(epgData => { this.lastActiveEPGDetails = activeEPGDetails = this.epgLoadingStatus(epgData) - epgNext() }).catch(err => { console.error(err) activeEPGDetails = '' - epgNext() - }) + }).finally(epgNext) } } else { next() @@ -614,6 +612,7 @@ class Manager extends ManagerEPG { this.lastProgress = 0 this.openingList = false global.uiReady(() => { + global.streamer.on('hard-failure', es => this.checkListExpiral(es)) global.explorer.addFilter(async (es, path) => { es = await this.expandEntries(es, path) es = this.master.tools.dedup(es) // apply dedup here again for expanded entries @@ -776,7 +775,7 @@ class Manager extends ManagerEPG { throw global.lang.INVALID_URL_MSG } } - async addList(value, name, skipSharing){ + async addList(value, name, fromCommunity){ let err const uid = parseInt(Math.random() * 100000) global.osd.show(global.lang.RECEIVING_LIST, 'fa-mega spin-x-alt', 'add-list-progress-'+ uid, 'persistent') @@ -792,18 +791,21 @@ class Manager extends ManagerEPG { throw err } else { global.osd.show(global.lang.LIST_ADDED, 'fas fa-check-circle', 'add-list', 'normal') + const protect = !fromCommunity && value.match(new RegExp('(pwd?|pass|password)=', 'i')) // protect sensible lists const currentEPG = global.config.get('epg-'+ global.lang.locale) - if(!skipSharing && value.match(new RegExp('(pwd|pass|password)=', 'i'))) { // protect sensitive lists - skipSharing = true - } - let chosen = (!skipSharing && global.validateURL(value)) ? await global.explorer.dialog([ + const community = global.config.get('communitary-mode-lists-amount') > 0 + const chosen = (!protect && community && global.validateURL(value)) ? await global.explorer.dialog([ {template: 'question', text: global.lang.COMMUNITY_LISTS, fa: 'fas fa-users'}, {template: 'message', text: global.lang.WANT_SHARE_COMMUNITY}, {template: 'option', text: lang.NO_THANKS, id: 'no', fa: 'fas fa-lock'}, {template: 'option', text: lang.SHARE, id: 'yes', fa: 'fas fa-users'} ], 'no') : 'no' // set local files as private - if(chosen == 'yes'){ + if(chosen == 'yes') { global.osd.show(global.lang.COMMUNITY_THANKS_YOU, 'fas fa-heart faclr-purple', 'communitary-lists-thanks', 'normal') + } else if(protect && community) { + // maybe the app should ask user about it, for now + // just disable to focus on user lists + global.config.set('communitary-mode-lists-amount', 0) } this.setMeta(value, 'private', chosen != 'yes') let info, i = 20 @@ -1182,6 +1184,7 @@ class Manager extends ManagerEPG { return await this.addList(url) } formatMacAddress(str) { + if(!str) return '' const mask = [] const filteredStr = str.replace(new RegExp('[^0-9a-fA-F]', 'g'), '').toUpperCase() for (let i = 0; i < 12; i += 2) { @@ -1246,11 +1249,15 @@ class Manager extends ManagerEPG { const usr = res[4], pw = res[5] return baseUrl +'/get.php?username='+ encodeURIComponent(usr) +'&password='+ encodeURIComponent(pw) +'&type=m3u&output=ts' } - isListExpired(url){ + async isListExpired(url, test){ if(this.master.loader.results[url]){ const ret = String(this.master.loader.results[url] || '') - return ret.substr(0, 6) == 'failed' && ['401', '403', '404', '410'].includes(result.substr(-3)) - } + return ret.startsWith('failed') && ['401', '403', '404', '410'].includes(ret.substr(-3)) + } + if(!test || !this.master.lists[url]) return false + this.master.lists[url].skipValidating = false + const connectable = await this.master.lists[url].verifyListQuality() + return !connectable } myListsEntry(manageOnly){ return { @@ -1261,16 +1268,17 @@ class Manager extends ManagerEPG { let lists = this.get() const extInfo = await this.master.info(true) const doNotShareHint = !global.config.get('communitary-mode-lists-amount') - let ls = lists.map(row => { + let ls = [] + for(const row of lists){ let url = row[1] if(!extInfo[url]) extInfo[url] = {} let name = extInfo[url].name || row[0] || this.nameFromSourceURL(url) let details = [extInfo[url].author || '', ' '+ global.kfmt(extInfo[url].length || 0)].filter(n => n).join('  ·  ') let icon = extInfo[url].icon || undefined let priv = (row.length > 2 && typeof(row[2]['private']) != 'undefined') ? row[2]['private'] : doNotShareHint - let expired = this.isListExpired(url) + let expired = await this.isListExpired(url, false) let flag = expired ? 'fas fa-exclamation-triangle faclr-red' : (priv ? 'fas fa-lock' : 'fas fa-users') - return { + ls.push({ prepend: ' ', name, url, icon, details, fa: 'fas fa-satellite-dish', @@ -1341,8 +1349,8 @@ class Manager extends ManagerEPG { }) return es } - } - }) + }) + } if(this.addingList){ ls.push({ name: global.lang.RECEIVING_LIST, @@ -1427,7 +1435,7 @@ class Manager extends ManagerEPG { global.explorer.back(1, true) } async directListRenderer(data, opts={}){ - let v = Object.assign({}, data), isMine = global.lists.activeLists.my.includes(v.url), isCommunity = global.lists.activeLists.community.includes(v.url) + let v = Object.assign({}, data) global.osd.show(global.lang.OPENING_LIST, 'fa-mega spin-x-alt', 'list-open', 'persistent') let list = await this.master.directListRenderer(v, { fetch: opts.fetch, @@ -1474,6 +1482,49 @@ class Manager extends ManagerEPG { global.osd.hide('list-open') return list } + checkListExpiral(es){ + if(!this.master.activeLists.my.length) return + const myBadSources = [...new Set(es.map(e => e.source).filter(e => e))].filter(u => this.master.activeLists.my.includes(u)) + for(const source of myBadSources) { + let expired + this.isListExpired(source, true).then(e => expired = e).catch(err => { + console.error(err) + expired = true // 'no valid links' error + }).finally(() => { + if(expired) { + const meta = this.master.lists[source].index.meta + const name = meta.name || meta.author || global.MANIFEST.name + const opts = [ + { template: 'question', text: name, fa: 'fas fa-exclamation-triangle faclr-red' }, + { template: 'message', text: global.lang.IPTV_LIST_EXPIRED +"\r\n\r\n"+ source }, + { template: 'option', text: 'OK', id: 'submit', fa: 'fas fa-check-circle' } + ] + let contactUrl, contactFa + if(meta.site) { + contactUrl = meta.site + contactFa = 'fas fa-globe' + } else if(meta.email) { + contactUrl = 'mailto:'+ meta.email + contactFa = 'fas fa-envelope' + } else if(meta.phone) { + contactUrl = 'tel:+'+ meta.phone.replace(new RegExp('[^0-9]+'), '') + contactFa = 'fas fa-phone' + } + if(contactUrl) { + opts.push({ + template: 'option', + text: global.lang.CONTACT_PROVIDER, + id: 'contact', + fa: contactFa + }) + } + global.explorer.dialog(opts).then(ret => { + if(ret == 'contact') global.ui.emit('open-external-url', contactUrl) + }).catch(console.error) + } + }) + } + } async hook(entries, path){ if(!path) { const entry = {name: global.lang.IPTV_LISTS, details: global.lang.CONFIGURE, fa: 'fas fa-list', type: 'group', renderer: this.listsEntries.bind(this)} diff --git a/www/nodejs-project/modules/lists/parser.js b/www/nodejs-project/modules/lists/parser.js index e4b97090..546b6595 100644 --- a/www/nodejs-project/modules/lists/parser.js +++ b/www/nodejs-project/modules/lists/parser.js @@ -170,7 +170,6 @@ class IPTVM3UParser extends EventEmitter { } this.reader.on('line', line => { this.readen += (line.length + 1) - console.error('LINE: '+ line) if(line.length < 6) return const hashed = line.charAt(0) === '#' if (hashed && this.isExtM3U(line)) { @@ -231,7 +230,7 @@ class IPTVM3UParser extends EventEmitter { } else if (hashed) { // parse here extra info like #EXTGRP and #EXTVLCOPT let lcline = line.toLowerCase() - if (lcline.indexOf('#EXTGRP') !== -1) { + if (lcline.startsWith('#extgrp') !== -1) { let i = lcline.indexOf(':') if (i !== -1) { let nwg = line.substr(i + 1).trim() @@ -239,7 +238,7 @@ class IPTVM3UParser extends EventEmitter { g = nwg } } - } else if (lcline.indexOf('#EXTVLCOPT') !== -1) { + } else if (lcline.startsWith('#extvlcopt') !== -1) { let i = lcline.indexOf(':') if (i !== -1) { let nwa = line.substr(i + 1).trim().split('=') @@ -249,7 +248,7 @@ class IPTVM3UParser extends EventEmitter { } } } - } else if (line.charAt(0) === '/' || line.substr(0, 7) === 'magnet:' || line.indexOf('://') !== -1) { + } else { // not hashed so, length already checked e.url = line if (e.url.startsWith('//')) { e.url = 'http:' + e.url @@ -329,22 +328,21 @@ class IPTVM3UParser extends EventEmitter { .replace(IPTVM3UParser.regexes['spaces'], ' ') } isExtInf(line) { - return String(line).toLowerCase().indexOf('#extinf') !== -1 + return line.charAt(0) == '#' && line.substr(0, 7).toLowerCase() == '#extinf' } isExtInfPlaylist(line) { - const l = String(line).toLowerCase() - return l.indexOf('#extinf') !== -1 && l.match(IPTVM3UParser.regexes['type-playlist']) + return this.isExtInf(line) && line.match(IPTVM3UParser.regexes['type-playlist']) } isExtM3U(line) { - let lcline = String(line).toLowerCase() - return lcline.indexOf('#extm3u') !== -1 || lcline.indexOf('#playlistv') !== -1 + let lcline = line.substr(0, 7).toLowerCase() + return lcline == '#extm3u' || lcline == '#playli' // #playlistv } trimQuotes(text) { - const quotes = ["'", '"'] - if (quotes.includes(text.charAt(0))) { + const f = text.charAt(0), l = text.charAt(text.length - 1) + if (f == '"' || f == "'") { text = text.substr(1) } - if (quotes.includes(text.charAt(text.length - 1))) { + if (l == '"' || l == "'") { text = text.substr(0, text.length - 1) } return text diff --git a/www/nodejs-project/modules/lists/update-list-index.js b/www/nodejs-project/modules/lists/update-list-index.js index d105dddc..31e8fa29 100644 --- a/www/nodejs-project/modules/lists/update-list-index.js +++ b/www/nodejs-project/modules/lists/update-list-index.js @@ -303,9 +303,14 @@ class UpdateListIndex extends ListIndexUtils { writer.on('finish', finish) writer.on('close', finish) writer.on('error', finish) + + const indexLine = JSON.stringify(this.index) +"\n" this.linesMap.push(this.linesMapPtr) - this.index.linesMap = this.linesMap - writer.write(JSON.stringify(this.index)) + this.linesMapPtr += Buffer.byteLength(indexLine, 'utf8') + this.linesMap.push(this.linesMapPtr) + + const linesMapLine = JSON.stringify(this.linesMap) + writer.write(indexLine + linesMapLine) writer.end() } else { resolved = true diff --git a/www/nodejs-project/modules/storage/storage.js b/www/nodejs-project/modules/storage/storage.js index 90b89d57..aef36c86 100644 --- a/www/nodejs-project/modules/storage/storage.js +++ b/www/nodejs-project/modules/storage/storage.js @@ -111,7 +111,7 @@ class StorageAsync extends StoragePromises { } this.expiration(key, expiral => { let now = global.time() - if(expiral > now){ + if(expiral > now) { if(this.cacheExpiration[key] != expiral){ this.cacheExpiration[key] = expiral } diff --git a/www/nodejs-project/modules/streamer/client.js b/www/nodejs-project/modules/streamer/client.js index a646db7d..30974a16 100644 --- a/www/nodejs-project/modules/streamer/client.js +++ b/www/nodejs-project/modules/streamer/client.js @@ -403,9 +403,9 @@ class StreamerUnmuteHack extends StreamerTranscode { // unmute player on browser } } unmuteHack(){ - parent.player.container.querySelectorAll('video, audio').forEach(e => { - e.muted = false - }) + if(this.unmuteHackApplied) return + this.unmuteHackApplied = true + parent.player.container.querySelectorAll('video, audio').forEach(e => e.muted = false) } } @@ -1212,6 +1212,11 @@ class StreamerAudioUI extends StreamerClientVideoFullScreen { this.isAudio = false parent.winman.backgroundModeUnlock('audio') this.jbody.removeClass('audio') + }) + this.on('state', s => { + if(s == 'playing' && this.muted) { + if(!parent.player.muted()) this.volumeMute() + } }) parent.player.on('audioTracks', tracks => this.app.emit('audioTracks', tracks)) parent.player.on('subtitleTracks', tracks => { @@ -1335,10 +1340,12 @@ class StreamerAudioUI extends StreamerClientVideoFullScreen { if(volume){ this.unmuteVolume = volume } + this.muted = true this.volumeInput.value = 0 this.volumeChanged() } volumeUnmute(){ + this.muted = false this.volumeInput.value = this.unmuteVolume || 100 this.volumeChanged() } diff --git a/www/nodejs-project/modules/streamer/streamer.js b/www/nodejs-project/modules/streamer/streamer.js index 5c7e1c12..737e970e 100644 --- a/www/nodejs-project/modules/streamer/streamer.js +++ b/www/nodejs-project/modules/streamer/streamer.js @@ -1059,7 +1059,7 @@ class Streamer extends StreamerAbout { if(!silent){ global.osd.show(global.lang.NONE_STREAM_WORKED_X.format(name), 'fas fa-exclamation-triangle faclr-red', 'streamer', 'normal') } - this.emit('hard-failure') + this.emit('hard-failure', entries) } else { this.setTuneable(true) } @@ -1275,7 +1275,7 @@ class Streamer extends StreamerAbout { } } } - this.emit('hard-failure', c, e) + this.emit('hard-failure', [e]) } humanizeFailureMessage(r){ r = String(r) diff --git a/www/nodejs-project/modules/supercharge/supercharge.js b/www/nodejs-project/modules/supercharge/supercharge.js index 5cc4924a..2702f831 100644 --- a/www/nodejs-project/modules/supercharge/supercharge.js +++ b/www/nodejs-project/modules/supercharge/supercharge.js @@ -108,13 +108,8 @@ function patch(scope){ } scope.validateURL = url => { if(url && url.length > 11){ - let u = url.toLowerCase() - if(u.match(new RegExp('^(https?://|//)', 'i'))){ - let domain = u.split('//')[1].split('/')[0] - if(domain.match(new RegExp('^[A-Za-z0-9_\\-\\.\\:@]{4,}$'))){ - return true - } - } + const parts = url.match(new RegExp('^(https?://|//)[A-Za-z0-9_\\-\\.\\:@]{4,}', 'i')) + return parts && parts.length } } scope.decodeURIComponentSafe = uri => { diff --git a/www/nodejs-project/modules/tuner/tuner.js b/www/nodejs-project/modules/tuner/tuner.js index e0551adb..ebfae8f5 100644 --- a/www/nodejs-project/modules/tuner/tuner.js +++ b/www/nodejs-project/modules/tuner/tuner.js @@ -15,7 +15,7 @@ class TunerUtils extends Events { this.setMaxListeners(64) this.paused = true this.opts = { - debug: false, + debug: true, shadow: false, allowedTypes: null } @@ -126,8 +126,10 @@ class TunerTask extends TunerUtils { return busy } nextEntry(){ - return new Promise((resolve, reject) => { + return new Promise(resolve => { + let timer = 0 const updateListener = () => { + clearTimeout(timer) if(this.paused) return let ret = -1 let busy = this.busyDomains() @@ -145,6 +147,8 @@ class TunerTask extends TunerUtils { this.removeListener('destroy', updateListener) this.results[ret] = 0 // ticket taken resolve(ret) + } else { + timer = setTimeout(updateListener, 5000) // busyDomains logic makes timer required yet } } this.on('resume', updateListener) @@ -184,13 +188,6 @@ class TunerTask extends TunerUtils { this.results[i] = 1 } this.pump() - let processing = this.entries.some((e, n) => typeof(this.results[n]) == 'undefined' || this.results[n] == 0) - if(!processing){ - if(this.opts.debug){ - console.log('Tuner end') - } - this.finish() - } } pause(){ if(!this.paused){ @@ -250,7 +247,7 @@ class TunerTask extends TunerUtils { } } finish(){ - console.warn('TUNER FINISH', traceback()) + console.error('TUNER FINISH', traceback()) if(!this.finished){ if(!this.aborted && !this.destroyed){ this.pump() diff --git a/www/nodejs-project/package.json b/www/nodejs-project/package.json index 8170a4ed..30ccdc05 100644 --- a/www/nodejs-project/package.json +++ b/www/nodejs-project/package.json @@ -46,7 +46,7 @@ "description": "A intuitive, multi-language and cross-platform IPTV player.", "name": "megacubo", "icon": "./default_icon.png", - "version": "17.0.7", + "version": "17.0.8", "theme": { "fullScreen": true },