diff --git a/background.js b/background.js
index 0449553..9408d4a 100644
--- a/background.js
+++ b/background.js
@@ -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: "" });
}
diff --git a/popup/main.html b/popup/main.html
index 87ead62..7a8879a 100644
--- a/popup/main.html
+++ b/popup/main.html
@@ -142,7 +142,7 @@
CrawlFlix Server
+ placeholder="http://localhost:4200" value="http://192.168.1.230:6080/">
diff --git a/popup/main.js b/popup/main.js
index d3ad82e..ea2c342 100644
--- a/popup/main.js
+++ b/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,77 +415,212 @@ class SmartMPDSelector {
}
class MPDDetector {
- static detectMPDUrls() {
- const backgroundPage = chrome.extension.getBackgroundPage();
- const detectedMPDs = backgroundPage.detectedMPDs || [];
-
- console.log('Raw detected MPDs:', detectedMPDs);
-
- return detectedMPDs
- .filter((url, index, arr) => arr.indexOf(url) === index) // Remove duplicates
- .filter(url => url && url.length > 0) // Remove empty URLs
- .filter(url => !url.includes('/chunk-') && !url.includes('/segment-')); // Filter segments
- }
+ constructor() {
+ this.cache = new MPDCache();
+ this.isUpdating = false;
+ }
- static updateBadgeFromPopup() {
- const backgroundPage = chrome.extension.getBackgroundPage();
- if (backgroundPage && backgroundPage.updateExtensionBadge) {
- backgroundPage.updateExtensionBadge();
- }
- }
+ static detectMPDUrls() {
+ const backgroundPage = chrome.extension.getBackgroundPage();
+ const detectedMPDs = backgroundPage.detectedMPDs || [];
+
+ console.log('Raw detected MPDs:', detectedMPDs);
+
+ return detectedMPDs
+ .filter((url, index, arr) => arr.indexOf(url) === index) // Remove duplicates
+ .filter(url => url && url.length > 0) // Remove empty URLs
+ .filter(url => !url.includes('/chunk-') && !url.includes('/segment-')); // Filter segments
+ }
- static updateDropdowns() {
- const mpdUrls = this.detectMPDUrls();
- const selects = ['mpdSelect', 'mpdSelectCK'];
-
- selects.forEach(selectId => {
- const select = document.getElementById(selectId);
- if (!select) return;
+ async updateDropdowns() {
+ // Éviter les mises à jour simultanées
+ if (this.isUpdating) {
+ console.log('MPD update already in progress, skipping...');
+ return;
+ }
- select.innerHTML = '';
-
- // Utiliser le smart selector pour trier les MPDs
- const rankedMPDs = SmartMPDSelector.scoreAndRankMPDs(mpdUrls);
-
- rankedMPDs.forEach((mpdInfo, index) => {
- const option = document.createElement('option');
- option.value = mpdInfo.url;
-
- // Ajouter des indicateurs visuels
- const prefix = index === 0 ? '⭐ [BEST] ' :
- mpdInfo.score > 0 ? '✓ ' : '⚠ ';
-
- option.textContent = prefix + this.formatUrlForDisplay(mpdInfo.url);
-
- // Ajouter des classes CSS pour styling
- if (index === 0) {
- option.style.fontWeight = 'bold';
- option.style.color = '#28a745';
- } else if (mpdInfo.score <= 0) {
- option.style.color = '#dc3545';
- }
-
- select.appendChild(option);
- });
- });
+ 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;
+ }
- // Auto-sélection intelligente
- if (mpdUrls.length > 0) {
- SmartMPDSelector.updateUIWithBestMPD(mpdUrls);
- }
+ 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);
+ }
+ });
- // Mettre à jour le status avec l'analyse
- StatusManager.show(
- `Found ${mpdUrls.length} MPD(s) - Auto-selected best candidate`,
- 'info'
- );
- }
+ // 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'
+ );
- static formatUrlForDisplay(url) {
- return SmartMPDSelector.truncateUrl(url, 80);
- }
+ 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;
+ }
+ }
+
+ 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 = '';
+
+ validMPDs.forEach((mpdInfo, index) => {
+ const option = document.createElement('option');
+ option.value = mpdInfo.url;
+
+ // 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}`;
+
+ // 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';
+ }
+
+ select.appendChild(option);
+ });
+ });
+
+ // Auto-sélection du meilleur
+ if (validMPDs.length > 0) {
+ this.selectBestMPD(validMPDs[0]);
+ }
+
+ // 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)`;
+ }
+
+ 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 = ' Refreshing...';
+
+ try {
+ await MPDDetector.forceUpdate();
+ } finally {
+ button.disabled = false;
+ button.innerHTML = ' 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
}
\ No newline at end of file