Some lil fixes + select best MPB based on video track duration
This commit is contained in:
@@ -32,11 +32,11 @@ function updateBadge() {
|
||||
const hasMPD = window.detectedMPDs && window.detectedMPDs.length > 0;
|
||||
|
||||
if (hasContent && hasMPD) {
|
||||
browserAction.setBadgeText({ text: "●" });
|
||||
browserAction.setBadgeBackgroundColor({ color: "#dc3545" });
|
||||
browserAction.setBadgeText({ text: detectedMPDs.length.toString() });
|
||||
browserAction.setBadgeBackgroundColor({ color: "#22c55e" });
|
||||
} else if (hasContent) {
|
||||
browserAction.setBadgeText({ text: "!" });
|
||||
browserAction.setBadgeBackgroundColor({ color: "#fd7e14" });
|
||||
browserAction.setBadgeTextColor({ color: "#fd7e14" });
|
||||
} else {
|
||||
browserAction.setBadgeText({ text: "" });
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
<i class="fas fa-server"></i> CrawlFlix Server
|
||||
</label>
|
||||
<input type="text" id="crawlFlixUrl" class="form-control"
|
||||
placeholder="http://localhost:4200" value="http://localhost:4200">
|
||||
placeholder="http://localhost:4200" value="http://192.168.1.230:6080/">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
@@ -237,7 +237,7 @@
|
||||
<div class="col-12">
|
||||
<label for="crawlFlixUrlCK" class="form-label">CrawlFlix Server</label>
|
||||
<input type="text" id="crawlFlixUrlCK" class="form-control form-control-sm"
|
||||
placeholder="http://localhost:4200" value="http://localhost:4200">
|
||||
placeholder="http://localhost:4200" value="http://192.168.1.230:6080/">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
331
popup/main.js
331
popup/main.js
@@ -111,6 +111,131 @@ class WidevineExtractor {
|
||||
}
|
||||
}
|
||||
|
||||
class MPDCache {
|
||||
constructor() {
|
||||
this.cachedMPDs = new Map();
|
||||
this.validationPromises = new Map();
|
||||
}
|
||||
|
||||
hasChanged(newMPDs) {
|
||||
const currentUrls = Array.from(this.cachedMPDs.keys()).sort();
|
||||
const newUrls = newMPDs.sort();
|
||||
|
||||
return JSON.stringify(currentUrls) !== JSON.stringify(newUrls);
|
||||
}
|
||||
|
||||
setCachedMPD(url, data) {
|
||||
this.cachedMPDs.set(url, {
|
||||
...data,
|
||||
lastValidated: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
// Récupère un MPD du cache
|
||||
getCachedMPD(url) {
|
||||
return this.cachedMPDs.get(url);
|
||||
}
|
||||
|
||||
// Supprime un MPD invalide du cache
|
||||
removeMPD(url) {
|
||||
this.cachedMPDs.delete(url);
|
||||
this.validationPromises.delete(url);
|
||||
}
|
||||
|
||||
// Récupère tous les MPDs valides triés par score/durée
|
||||
getValidMPDs() {
|
||||
return Array.from(this.cachedMPDs.entries())
|
||||
.map(([url, data]) => ({ url, ...data }))
|
||||
.sort((a, b) => {
|
||||
// Prioriser d'abord par durée si disponible
|
||||
if (a.duration && b.duration) {
|
||||
return b.duration - a.duration;
|
||||
}
|
||||
// Sinon par score
|
||||
return b.score - a.score;
|
||||
});
|
||||
}
|
||||
|
||||
cleanup(maxAge = 5 * 60 * 1000) {
|
||||
const now = Date.now();
|
||||
for (const [url, data] of this.cachedMPDs.entries()) {
|
||||
if (now - data.lastValidated > maxAge) {
|
||||
this.removeMPD(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MPDValidator {
|
||||
static async validateMPD(url) {
|
||||
const API_URL = 'http://192.168.1.230:6080/api/processMpd';
|
||||
|
||||
try {
|
||||
const response = await fetch(API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
mp4Filename: "validation_test",
|
||||
mpdUrl: url
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API returned ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// Valider la structure de la réponse
|
||||
if (!result.videoTracks || !Array.isArray(result.videoTracks)) {
|
||||
throw new Error('Invalid response structure: missing videoTracks array');
|
||||
}
|
||||
|
||||
// Calculer la durée maximale parmi les tracks
|
||||
const maxDuration = result.videoTracks.reduce((max, track) => {
|
||||
const duration = track.duration || 0;
|
||||
return Math.max(max, duration);
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
duration: maxDuration,
|
||||
videoTracks: result.videoTracks,
|
||||
raw: result
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error(`MPD validation failed for ${url}:`, error);
|
||||
return {
|
||||
valid: false,
|
||||
error: error.message,
|
||||
duration: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static async validateMPDWithCache(url, cache) {
|
||||
// Vérifier si on a déjà une promesse en cours pour cette URL
|
||||
if (cache.validationPromises.has(url)) {
|
||||
return await cache.validationPromises.get(url);
|
||||
}
|
||||
|
||||
// Créer une nouvelle promesse de validation
|
||||
const validationPromise = this.validateMPD(url);
|
||||
cache.validationPromises.set(url, validationPromise);
|
||||
|
||||
try {
|
||||
const result = await validationPromise;
|
||||
return result;
|
||||
} finally {
|
||||
// Nettoyer la promesse une fois terminée
|
||||
cache.validationPromises.delete(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SmartMPDSelector {
|
||||
static scoreAndRankMPDs(mpdUrls) {
|
||||
const scoredMPDs = mpdUrls.map(url => ({
|
||||
@@ -290,6 +415,11 @@ class SmartMPDSelector {
|
||||
}
|
||||
|
||||
class MPDDetector {
|
||||
constructor() {
|
||||
this.cache = new MPDCache();
|
||||
this.isUpdating = false;
|
||||
}
|
||||
|
||||
static detectMPDUrls() {
|
||||
const backgroundPage = chrome.extension.getBackgroundPage();
|
||||
const detectedMPDs = backgroundPage.detectedMPDs || [];
|
||||
@@ -302,40 +432,135 @@ class MPDDetector {
|
||||
.filter(url => !url.includes('/chunk-') && !url.includes('/segment-')); // Filter segments
|
||||
}
|
||||
|
||||
static updateBadgeFromPopup() {
|
||||
const backgroundPage = chrome.extension.getBackgroundPage();
|
||||
if (backgroundPage && backgroundPage.updateExtensionBadge) {
|
||||
backgroundPage.updateExtensionBadge();
|
||||
async updateDropdowns() {
|
||||
// Éviter les mises à jour simultanées
|
||||
if (this.isUpdating) {
|
||||
console.log('MPD update already in progress, skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isUpdating = true;
|
||||
|
||||
try {
|
||||
const rawMPDs = MPDDetector.detectMPDUrls();
|
||||
|
||||
// Vérifier si la liste a changé
|
||||
if (!this.cache.hasChanged(rawMPDs)) {
|
||||
console.log('MPD list unchanged, using cache');
|
||||
this.renderDropdowns();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('MPD list changed, updating cache...');
|
||||
|
||||
// Nettoyer le cache des URLs qui ne sont plus détectées
|
||||
const currentCachedUrls = Array.from(this.cache.cachedMPDs.keys());
|
||||
currentCachedUrls.forEach(url => {
|
||||
if (!rawMPDs.includes(url)) {
|
||||
this.cache.removeMPD(url);
|
||||
}
|
||||
});
|
||||
|
||||
// Traiter les nouvelles URLs
|
||||
const newUrls = rawMPDs.filter(url => !this.cache.getCachedMPD(url));
|
||||
|
||||
if (newUrls.length > 0) {
|
||||
StatusManager.show(
|
||||
`Validating ${newUrls.length} new MPD(s)...`,
|
||||
'info'
|
||||
);
|
||||
|
||||
await this.processNewMPDs(newUrls);
|
||||
}
|
||||
|
||||
// Rendre les dropdowns
|
||||
this.renderDropdowns();
|
||||
|
||||
// Nettoyer le cache périodiquement
|
||||
this.cache.cleanup();
|
||||
|
||||
} catch (error) {
|
||||
console.error('MPD update failed:', error);
|
||||
StatusManager.show(`MPD update failed: ${error.message}`, 'error');
|
||||
} finally {
|
||||
this.isUpdating = false;
|
||||
}
|
||||
}
|
||||
|
||||
static updateDropdowns() {
|
||||
const mpdUrls = this.detectMPDUrls();
|
||||
async processNewMPDs(urls) {
|
||||
const validationPromises = urls.map(async (url) => {
|
||||
try {
|
||||
// Calculer le score initial (logique existante)
|
||||
const initialScore = SmartMPDSelector.calculateMPDScore(url);
|
||||
const reason = SmartMPDSelector.getSelectionReason(url);
|
||||
|
||||
// Valider via API
|
||||
const validationResult = await MPDValidator.validateMPDWithCache(url, this.cache);
|
||||
|
||||
if (validationResult.valid) {
|
||||
// Ajuster le score basé sur la durée
|
||||
let finalScore = initialScore;
|
||||
if (validationResult.duration > 0) {
|
||||
// Bonus pour les durées plus longues
|
||||
finalScore += Math.min(validationResult.duration / 10, 500); // Max +500 points
|
||||
}
|
||||
|
||||
this.cache.setCachedMPD(url, {
|
||||
score: finalScore,
|
||||
reason: `${reason} | Duration: ${validationResult.duration}s`,
|
||||
duration: validationResult.duration,
|
||||
valid: true,
|
||||
videoTracks: validationResult.videoTracks
|
||||
});
|
||||
|
||||
console.log(`✅ MPD validated: ${url} (duration: ${validationResult.duration}s)`);
|
||||
} else {
|
||||
// MPD invalide, ne pas l'ajouter au cache
|
||||
console.log(`❌ MPD invalid: ${url} (${validationResult.error})`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing MPD ${url}:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(validationPromises);
|
||||
}
|
||||
|
||||
renderDropdowns() {
|
||||
const validMPDs = this.cache.getValidMPDs();
|
||||
const selects = ['mpdSelect', 'mpdSelectCK'];
|
||||
|
||||
selects.forEach(selectId => {
|
||||
const select = document.getElementById(selectId);
|
||||
if (!select) return;
|
||||
|
||||
select.innerHTML = '<option value="">-- Auto-detected MPDs --</option>';
|
||||
select.innerHTML = '<option value="">-- Validated MPDs --</option>';
|
||||
|
||||
// Utiliser le smart selector pour trier les MPDs
|
||||
const rankedMPDs = SmartMPDSelector.scoreAndRankMPDs(mpdUrls);
|
||||
|
||||
rankedMPDs.forEach((mpdInfo, index) => {
|
||||
validMPDs.forEach((mpdInfo, index) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = mpdInfo.url;
|
||||
|
||||
// Ajouter des indicateurs visuels
|
||||
const prefix = index === 0 ? '⭐ [BEST] ' :
|
||||
mpdInfo.score > 0 ? '✓ ' : '⚠ ';
|
||||
// Indicateurs visuels améliorés
|
||||
let prefix = '';
|
||||
if (index === 0) {
|
||||
prefix = `🏆 [${mpdInfo.duration}s] `;
|
||||
} else if (mpdInfo.duration > 0) {
|
||||
prefix = `⏱️ [${mpdInfo.duration}s] `;
|
||||
} else if (mpdInfo.score > 0) {
|
||||
prefix = '✅ ';
|
||||
} else {
|
||||
prefix = '⚠️ ';
|
||||
}
|
||||
|
||||
option.textContent = prefix + this.formatUrlForDisplay(mpdInfo.url);
|
||||
option.title = `Score: ${mpdInfo.score} | ${mpdInfo.reason}`;
|
||||
|
||||
// Ajouter des classes CSS pour styling
|
||||
// Styling
|
||||
if (index === 0) {
|
||||
option.style.fontWeight = 'bold';
|
||||
option.style.color = '#28a745';
|
||||
} else if (mpdInfo.duration > 1000) { // Plus de 16 minutes
|
||||
option.style.color = '#17a2b8';
|
||||
} else if (mpdInfo.score <= 0) {
|
||||
option.style.color = '#dc3545';
|
||||
}
|
||||
@@ -344,23 +569,58 @@ class MPDDetector {
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-sélection intelligente
|
||||
if (mpdUrls.length > 0) {
|
||||
SmartMPDSelector.updateUIWithBestMPD(mpdUrls);
|
||||
// Auto-sélection du meilleur
|
||||
if (validMPDs.length > 0) {
|
||||
this.selectBestMPD(validMPDs[0]);
|
||||
}
|
||||
|
||||
// Mettre à jour le status avec l'analyse
|
||||
StatusManager.show(
|
||||
`Found ${mpdUrls.length} MPD(s) - Auto-selected best candidate`,
|
||||
'info'
|
||||
);
|
||||
// Mettre à jour le status
|
||||
const totalDetected = MPDDetector.detectMPDUrls().length;
|
||||
const validCount = validMPDs.length;
|
||||
const invalidCount = totalDetected - validCount;
|
||||
|
||||
let statusMessage = `Found ${validCount} valid MPD(s)`;
|
||||
if (invalidCount > 0) {
|
||||
statusMessage += ` (${invalidCount} filtered out)`;
|
||||
}
|
||||
|
||||
static formatUrlForDisplay(url) {
|
||||
StatusManager.show(statusMessage, validCount > 0 ? 'success' : 'warning');
|
||||
}
|
||||
|
||||
selectBestMPD(bestMPD) {
|
||||
const selects = ['mpdSelect', 'mpdSelectCK'];
|
||||
const inputs = ['mpdUrl', 'mpdUrlCK'];
|
||||
|
||||
selects.forEach((selectId, index) => {
|
||||
const select = document.getElementById(selectId);
|
||||
const input = document.getElementById(inputs[index]);
|
||||
|
||||
if (select && input) {
|
||||
select.value = bestMPD.url;
|
||||
input.value = bestMPD.url;
|
||||
|
||||
// Effet visuel
|
||||
select.classList.add('border-success');
|
||||
setTimeout(() => select.classList.remove('border-success'), 3000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
formatUrlForDisplay(url) {
|
||||
return SmartMPDSelector.truncateUrl(url, 80);
|
||||
}
|
||||
|
||||
// Méthode publique pour forcer la mise à jour
|
||||
async forceUpdate() {
|
||||
this.cache = new MPDCache(); // Reset du cache
|
||||
await this.updateDropdowns();
|
||||
}
|
||||
}
|
||||
|
||||
const mpdDetectorInstance = new MPDDetector();
|
||||
MPDDetector.updateDropdowns = () => mpdDetectorInstance.updateDropdowns();
|
||||
MPDDetector.forceUpdate = () => mpdDetectorInstance.forceUpdate();
|
||||
|
||||
// === CRAWLFLIX INTEGRATION ===
|
||||
class CrawlFlixIntegration {
|
||||
static async sendToCrawlFlix(isClearchey = false) {
|
||||
@@ -617,7 +877,17 @@ class EventManager {
|
||||
['refreshMPDs', 'refreshMPDsCK'].forEach(buttonId => {
|
||||
const button = document.getElementById(buttonId);
|
||||
if (button) {
|
||||
button.addEventListener('click', () => MPDDetector.updateDropdowns());
|
||||
button.addEventListener('click', async () => {
|
||||
button.disabled = true;
|
||||
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Refreshing...';
|
||||
|
||||
try {
|
||||
await MPDDetector.forceUpdate();
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.innerHTML = '<i class="fas fa-sync"></i> Refresh';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -693,15 +963,14 @@ if (clearkey) {
|
||||
document.getElementById('noEME').style.display = 'none';
|
||||
document.getElementById('home').style.display = 'block';
|
||||
|
||||
// IMPORTANT: Faire autoSelect avant d'initialiser les event listeners
|
||||
WidevineExtractor.autoSelect().then(() => {
|
||||
WidevineExtractor.autoSelect().then(async () => {
|
||||
EventManager.init();
|
||||
MPDDetector.updateDropdowns();
|
||||
await MPDDetector.updateDropdowns();
|
||||
StatusManager.show('Widevine content detected and configured', 'success');
|
||||
});
|
||||
|
||||
// Auto-refresh MPD detection periodically
|
||||
// Réduire la fréquence de refresh automatique
|
||||
setInterval(() => {
|
||||
MPDDetector.updateDropdowns();
|
||||
}, 3000);
|
||||
MPDDetector.updateDropdowns(); // Ne se met à jour que si nécessaire
|
||||
}, 60000); // Augmenté à 10 secondes au lieu de 3
|
||||
}
|
||||
Reference in New Issue
Block a user