MovixOpenSource/index.html
Movix 4bba4cab40 chore: drop firebase + perf wave + mobile v2.5.3 + uqload HLS + multi-receiver cast + SW gateway
Nettoyage massif côté frontend, grosse passe perf, app mobile v2.5.3
(écran qui ne s'éteint plus + fix build), extracteur Uqload HLS sur
extensions/userscript, redirection réseau-bloqué via passerelle
explicative et cast cross-receiver/cross-browser.

Nettoyage Firebase & code mort
- Drop firebase + @firebase/firestore (package.json + vite.config
  optimizeDeps + alias)
- Suppression src/config/firebase.ts (jamais utilisé)
- Suppression services morts : adminService, commentService, logService,
  notificationService + composants liés (AdminLogin, CommentSection,
  CommentItem, ReactionBar) + types/Comment.ts
- Suppression scripts non-utilisés : bundle-report.mjs, publish-app.mjs
- Net : -3 449 lignes

Perf carrousels & TMDB images
- useTmdbImages : 1 seul fetch TMDB pour logo + poster (au lieu de 2),
  prefetch idle au mount du carousel + dedup via inflight map (0 doublon
  entre prefetch et hooks), include_image_language=fr,en,null pour
  récupérer les versions FR-prioritisées
- EmblaCarousel : pré-décode des posters off-DOM (img.decode) en idle
  pour éliminer le coût de décode pendant le scroll horizontal, priority
  cap statique (eager + fetchpriority=high pour les N premières cards
  selon viewport), suppression du tracking visibleSlides (state churn)
- content-visibility:auto sur slides hors viewport (skip layout/paint
  natif), exclu des slides Top 10 pour ne pas clipper le digit débordant
- will-change:transform pré-promotion compositing layer (PC only via
  hover:hover)
- Embla duration 25 → 15 (snap plus rapide, moins de frames composées)
- Boutons nav carousel : suppression backdrop-blur (cher), bg opaque
- LazyImage : width/height attrs pour éviter CLS + alléger placeholder

Top progress bar (style YouTube)
- TopProgressBar : asymptote vers 85% pendant chunk loads, fill 100%
  smooth + fade out à la fin, 120ms grace period pour suppress flash
  sur chunks cachés
- lazyWithRetry tracke les loads actifs + dispatch chunk:load:start /
  chunk:load:end events sur window
- Routes loaders supportent { silent: true } pour skip le tracking sur
  les prefetch (PrefetchLink hover + IDLE_PREFETCH au mount)

SW redirect → passerelle movix.health explicative
- buildMirrorUrl() factorise la construction d'URL miroir avec contexte
- handleNavigation passe ?from=&reason=unreachable&error=&via= à la
  gateway pour qu'elle affiche une popup explicative au user au lieu
  d'un redirect silencieux
- MOVIX_FORCE_REDIRECT message handler passe reason=api-errors
- blockDetection.ts transmet la dernière erreur axios au SW
- index.html : cache-bust du Cast SDK URL (cb=Date.now()) pour éviter
  qu'un SDK stale ne reste coincé en cache navigateur/SW

Cast (Chromecast + AirPlay + Remote Playback API)
- loadMediaOnCastWithFallback : retry sur 3 MIME HLS différents
  (application/vnd.apple.mpegurl, x-mpegurl, x-mpegURL) — certains
  receivers rejettent le premier choix selon firmware
- Subtitles externes attachés au cast (CastSubtitleTrack) via
  chrome.cast.media.Track + TextTrackStyle par défaut (50% black
  background, edge outline) lisible sur la majorité des TV
- isRemotePlaybackSupported : fallback W3C Remote Playback API pour
  Firefox / Edge avec cast extension bloquée / Brave avec shields →
  device discovery + picker via video.remote.prompt()
- initializeAirPlay supporte désormais les deux paths (WebKit AirPlay
  pour Safari + Remote Playback pour les autres)
- Détection castSdkBlocked : si le SDK Cast ne charge pas en 5s
  (adblock, FAI, shield), affiche un message d'erreur explicite au
  lieu du générique "aucun device trouvé"
- 7 nouvelles strings i18n FR + EN pour les états cast :
  castUnavailable, castUnavailableNoDevices,
  castUnavailableUnsupportedBrowser, castUnavailableSdkBlocked,
  castUnavailableHelpChromecast, castUnavailableHelpAirPlay,
  castUnavailableSeeHelp

App mobile Android v2.5.3 (versionCode 12)
- AndroidManifest : android:keepScreenOn="true" sur MainActivity →
  l'écran ne s'éteint plus pendant le visionnage (avant le user devait
  toucher l'écran toutes les 30s pour pas que le téléphone se lock en
  plein film)
- build.gradle : exclude androidx.legacy:legacy-support-core-utils
  (tiré transitivement par mediarouter → palette) qui embarquait ses
  propres androidx.autofill.R$attr → "duplicate class" au mergeDexRelease
  contre le module androidx.autofill:autofill:1.1.0 d'appcompat
- gradle.properties : android.nonTransitiveRClass=true pour empêcher
  chaque lib de regénérer ses R classes transitivement (même cause
  racine du duplicate class)
- version.json bumped 2.5.2 → 2.5.3, nouvel APK (-17KB), release notes
  FR : "Correction extraction du lecteur Uqload" + "L'écran ne s'éteint
  plus lors de l'utilisation de l'app"
- apkUrl repointé sur le repo Movix-STMG (au lieu de movixcorp)

Userscript + Extensions Chrome/Firefox — Uqload HLS
- extractUqload : préfère désormais le master.m3u8 multi-bitrate (qualité
  adaptative) au v.mp4 single-quality. Regex tente d'abord
  /master\.m3u8/, sinon n'importe quel .m3u8, et fallback sur l'ancien
  pattern v.mp4 si rien trouvé. Les 3 cibles (Chrome MV3, Firefox MV2,
  Tampermonkey) sont synchronisées sur la même logique.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:28:51 +02:00

202 lines
7.5 KiB
HTML

<!DOCTYPE html>
<html lang="fr" class="dark" translate="no">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta name="robots" content="index, follow, noarchive, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
<meta name="google" content="notranslate">
<meta name="apple-mobile-web-app-title" content="Movix" />
<meta name="application-name" content="Movix" />
<meta name="msapplication-TileColor" content="#000000" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Regardez des milliers de films et séries en streaming HD sur Movix. Catalogue complet en VF et VOSTFR, live TV, collections et watch parties synchronisées entre amis." />
<!-- Prefetch -->
<link rel="dns-prefetch" href="//image.tmdb.org">
<link rel="preconnect" href="https://image.tmdb.org" crossorigin>
<link rel="dns-prefetch" href="//www.gstatic.com">
<link rel="dns-prefetch" href="//www.google.com">
<link rel="preconnect" href="https://www.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="//www.googletagmanager.com">
<link rel="preconnect" href="https://www.googletagmanager.com">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- Icons -->
<link rel="icon" href="/movix.png" />
<link rel="icon" type="image/png" href="/movix.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/movix.png" />
<link rel="shortcut icon" href="/movix.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/movix.png" />
<link rel="manifest" href="/manifest.json" />
<title data-rh="true">Movix — Films et séries en streaming gratuit | VF, VOSTFR, HD</title>
<!-- JSON-LD -->
<script type="application/ld+json" data-rh="true">
[
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Movix",
"alternateName": "Movix Streaming",
"url": "https://movix.cash",
"description": "Movix est une plateforme française open-source (CC BY-NC 4.0) de streaming multimédia : films, séries, anime, TV en direct et Watch Parties synchronisées.",
"logo": {
"@type": "ImageObject",
"url": "https://movix.cash/movix.png",
"width": 512,
"height": 512
},
"sameAs": [
"https://t.me/movix_site"
],
"contactPoint": {
"@type": "ContactPoint",
"contactType": "customer support",
"email": "movixstreaming@gmail.com",
"availableLanguage": ["French", "English"]
}
},
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Movix",
"url": "https://movix.cash",
"inLanguage": "fr",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://movix.cash/search?q={search_term_string}"
},
"query-input": "required name=search_term_string"
}
}
]
</script>
<script>
// Object.hasOwn is ES2022 (Chrome 93+, Safari 15.4+, Firefox 92+). react-markdown
// calls it directly; older Chromium builds (e.g. Chrome 88 on armv7l) crash without this.
if (typeof Object.hasOwn !== 'function') {
Object.defineProperty(Object, 'hasOwn', {
value: function (obj, prop) {
if (obj === null || obj === undefined) {
throw new TypeError('Cannot convert undefined or null to object');
}
return Object.prototype.hasOwnProperty.call(Object(obj), prop);
},
configurable: true,
writable: true,
});
}
</script>
<script>
(() => {
const buildId = '%VITE_APP_BUILD_ID%';
const buildStorageKey = 'movix_build_id';
const recoveryStorageKey = `movix_build_recovery_${buildId}`;
const refreshSearchParam = 'movix_build_refresh';
const buildRecoveryUrl = () => {
const url = new URL(window.location.href);
url.searchParams.set(refreshSearchParam, `${buildId}-${Date.now()}`);
return url.toString();
};
const recoverClientCache = async () => {
try {
if ('serviceWorker' in navigator) {
const registrations = await navigator.serviceWorker.getRegistrations();
await Promise.all(registrations.map((registration) => registration.unregister()));
}
if ('caches' in window) {
const cacheKeys = await caches.keys();
await Promise.all(cacheKeys.map((cacheKey) => caches.delete(cacheKey)));
}
} catch {
// Ignore cleanup errors and continue with reload.
}
try {
localStorage.setItem(buildStorageKey, buildId);
sessionStorage.setItem(recoveryStorageKey, 'done');
} catch {
// Ignore storage errors.
}
window.location.replace(buildRecoveryUrl());
};
try {
const currentUrl = new URL(window.location.href);
const previousBuildId = localStorage.getItem(buildStorageKey);
const alreadyRecovered = sessionStorage.getItem(recoveryStorageKey) === 'done';
if (previousBuildId && previousBuildId !== buildId && !alreadyRecovered) {
window.__MOVIX_CACHE_RECOVERY__ = recoverClientCache();
return;
}
localStorage.setItem(buildStorageKey, buildId);
if (currentUrl.searchParams.has(refreshSearchParam)) {
currentUrl.searchParams.delete(refreshSearchParam);
window.history.replaceState({}, document.title, currentUrl.toString());
}
} catch {
// Ignore storage errors.
}
window.addEventListener('error', (event) => {
const target = event.target;
if (!(target instanceof HTMLScriptElement)) return;
if (target.type !== 'module') return;
if (!target.src.includes('/assets/')) return;
try {
if (sessionStorage.getItem(recoveryStorageKey) === 'done') {
return;
}
} catch {
// Ignore storage errors.
}
window.__MOVIX_CACHE_RECOVERY__ = recoverClientCache();
}, true);
})();
</script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>
<script type="module" src="/src/main.tsx"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-6Q3B4D5KQL"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-6Q3B4D5KQL');
</script>
</head>
<body>
<div id="root"></div>
<script>
// Cache-bust the Cast SDK URL on every page load so a stale SDK never
// sticks around in the browser/SW cache (defensive — gstatic already
// sets sane Cache-Control, but cinepulse does this too).
(function loadCastSender() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1&cb=' + Date.now();
document.body.appendChild(s);
})();
</script>
</body>
</html>