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;
|
const hasMPD = window.detectedMPDs && window.detectedMPDs.length > 0;
|
||||||
|
|
||||||
if (hasContent && hasMPD) {
|
if (hasContent && hasMPD) {
|
||||||
browserAction.setBadgeText({ text: "●" });
|
browserAction.setBadgeText({ text: detectedMPDs.length.toString() });
|
||||||
browserAction.setBadgeBackgroundColor({ color: "#dc3545" });
|
browserAction.setBadgeBackgroundColor({ color: "#22c55e" });
|
||||||
} else if (hasContent) {
|
} else if (hasContent) {
|
||||||
browserAction.setBadgeText({ text: "!" });
|
browserAction.setBadgeText({ text: "!" });
|
||||||
browserAction.setBadgeBackgroundColor({ color: "#fd7e14" });
|
browserAction.setBadgeTextColor({ color: "#fd7e14" });
|
||||||
} else {
|
} else {
|
||||||
browserAction.setBadgeText({ text: "" });
|
browserAction.setBadgeText({ text: "" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@
|
|||||||
<i class="fas fa-server"></i> CrawlFlix Server
|
<i class="fas fa-server"></i> CrawlFlix Server
|
||||||
</label>
|
</label>
|
||||||
<input type="text" id="crawlFlixUrl" class="form-control"
|
<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>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label for="crawlFlixUrlCK" class="form-label">CrawlFlix Server</label>
|
<label for="crawlFlixUrlCK" class="form-label">CrawlFlix Server</label>
|
||||||
<input type="text" id="crawlFlixUrlCK" class="form-control form-control-sm"
|
<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>
|
||||||
</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 {
|
class SmartMPDSelector {
|
||||||
static scoreAndRankMPDs(mpdUrls) {
|
static scoreAndRankMPDs(mpdUrls) {
|
||||||
const scoredMPDs = mpdUrls.map(url => ({
|
const scoredMPDs = mpdUrls.map(url => ({
|
||||||
@@ -290,6 +415,11 @@ class SmartMPDSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MPDDetector {
|
class MPDDetector {
|
||||||
|
constructor() {
|
||||||
|
this.cache = new MPDCache();
|
||||||
|
this.isUpdating = false;
|
||||||
|
}
|
||||||
|
|
||||||
static detectMPDUrls() {
|
static detectMPDUrls() {
|
||||||
const backgroundPage = chrome.extension.getBackgroundPage();
|
const backgroundPage = chrome.extension.getBackgroundPage();
|
||||||
const detectedMPDs = backgroundPage.detectedMPDs || [];
|
const detectedMPDs = backgroundPage.detectedMPDs || [];
|
||||||
@@ -302,40 +432,135 @@ class MPDDetector {
|
|||||||
.filter(url => !url.includes('/chunk-') && !url.includes('/segment-')); // Filter segments
|
.filter(url => !url.includes('/chunk-') && !url.includes('/segment-')); // Filter segments
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateBadgeFromPopup() {
|
async updateDropdowns() {
|
||||||
const backgroundPage = chrome.extension.getBackgroundPage();
|
// Éviter les mises à jour simultanées
|
||||||
if (backgroundPage && backgroundPage.updateExtensionBadge) {
|
if (this.isUpdating) {
|
||||||
backgroundPage.updateExtensionBadge();
|
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() {
|
async processNewMPDs(urls) {
|
||||||
const mpdUrls = this.detectMPDUrls();
|
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'];
|
const selects = ['mpdSelect', 'mpdSelectCK'];
|
||||||
|
|
||||||
selects.forEach(selectId => {
|
selects.forEach(selectId => {
|
||||||
const select = document.getElementById(selectId);
|
const select = document.getElementById(selectId);
|
||||||
if (!select) return;
|
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
|
validMPDs.forEach((mpdInfo, index) => {
|
||||||
const rankedMPDs = SmartMPDSelector.scoreAndRankMPDs(mpdUrls);
|
|
||||||
|
|
||||||
rankedMPDs.forEach((mpdInfo, index) => {
|
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = mpdInfo.url;
|
option.value = mpdInfo.url;
|
||||||
|
|
||||||
// Ajouter des indicateurs visuels
|
// Indicateurs visuels améliorés
|
||||||
const prefix = index === 0 ? '⭐ [BEST] ' :
|
let prefix = '';
|
||||||
mpdInfo.score > 0 ? '✓ ' : '⚠ ';
|
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.textContent = prefix + this.formatUrlForDisplay(mpdInfo.url);
|
||||||
|
option.title = `Score: ${mpdInfo.score} | ${mpdInfo.reason}`;
|
||||||
|
|
||||||
// Ajouter des classes CSS pour styling
|
// Styling
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
option.style.fontWeight = 'bold';
|
option.style.fontWeight = 'bold';
|
||||||
option.style.color = '#28a745';
|
option.style.color = '#28a745';
|
||||||
|
} else if (mpdInfo.duration > 1000) { // Plus de 16 minutes
|
||||||
|
option.style.color = '#17a2b8';
|
||||||
} else if (mpdInfo.score <= 0) {
|
} else if (mpdInfo.score <= 0) {
|
||||||
option.style.color = '#dc3545';
|
option.style.color = '#dc3545';
|
||||||
}
|
}
|
||||||
@@ -344,22 +569,57 @@ class MPDDetector {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-sélection intelligente
|
// Auto-sélection du meilleur
|
||||||
if (mpdUrls.length > 0) {
|
if (validMPDs.length > 0) {
|
||||||
SmartMPDSelector.updateUIWithBestMPD(mpdUrls);
|
this.selectBestMPD(validMPDs[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mettre à jour le status avec l'analyse
|
// Mettre à jour le status
|
||||||
StatusManager.show(
|
const totalDetected = MPDDetector.detectMPDUrls().length;
|
||||||
`Found ${mpdUrls.length} MPD(s) - Auto-selected best candidate`,
|
const validCount = validMPDs.length;
|
||||||
'info'
|
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);
|
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 ===
|
// === CRAWLFLIX INTEGRATION ===
|
||||||
class CrawlFlixIntegration {
|
class CrawlFlixIntegration {
|
||||||
@@ -617,7 +877,17 @@ class EventManager {
|
|||||||
['refreshMPDs', 'refreshMPDsCK'].forEach(buttonId => {
|
['refreshMPDs', 'refreshMPDsCK'].forEach(buttonId => {
|
||||||
const button = document.getElementById(buttonId);
|
const button = document.getElementById(buttonId);
|
||||||
if (button) {
|
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('noEME').style.display = 'none';
|
||||||
document.getElementById('home').style.display = 'block';
|
document.getElementById('home').style.display = 'block';
|
||||||
|
|
||||||
// IMPORTANT: Faire autoSelect avant d'initialiser les event listeners
|
WidevineExtractor.autoSelect().then(async () => {
|
||||||
WidevineExtractor.autoSelect().then(() => {
|
|
||||||
EventManager.init();
|
EventManager.init();
|
||||||
MPDDetector.updateDropdowns();
|
await MPDDetector.updateDropdowns();
|
||||||
StatusManager.show('Widevine content detected and configured', 'success');
|
StatusManager.show('Widevine content detected and configured', 'success');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-refresh MPD detection periodically
|
// Réduire la fréquence de refresh automatique
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
MPDDetector.updateDropdowns();
|
MPDDetector.updateDropdowns(); // Ne se met à jour que si nécessaire
|
||||||
}, 3000);
|
}, 60000); // Augmenté à 10 secondes au lieu de 3
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user