11521 sujets

JavaScript, DOM et API Web HTML5

Hello,

j'ai plusieurs videos sur une page avec leur source respective dans un data-src de la balise video.
J'arrive bien à les précharger via une requete xhr mais je n'arrive pas à les jouer seulement quand elles sont toutes chargées. Via mon code ci-dessous, elles se jouent forcément les unes après les autres. Je n'arrive pas à sortir le play() de la boucle en fait.
Je ne sais pas trop comment m'y prendre... j'ai besoin d'une lecture simultanée et synchronisée.

Merci pour vos tuyaux.


function preload_videos() {
    let all_videos = document.querySelectorAll('video');
                
    all_videos.forEach((video) => {
        var url = video.getAttribute('data-src');

        var xhrReq = new XMLHttpRequest();
        xhrReq.open('GET', url, true);
        xhrReq.responseType = 'blob';

        xhrReq.onload = function() {
            if (this.status === 200) {
                var videoBlob = this.response;
                var vid = URL.createObjectURL(videoBlob);
                video.src = vid;
                video.play();
            }
        }

        xhrReq.onerror = function() {
            console.log('erreur');
        }
           
        xhrReq.send();
    });
}

document.addEventListener("DOMContentLoaded", () => {
    preload_videos();
});

Modifié par Pedrothelion (24 May 2024 - 20:30)
Salut,

J'ai réécris votre fonction avec `fetch()` car ce protocole est plus moderne et désormais supporté partout (dans le même ordre d'idée je suis passé du snake_case au camelCase). Pour l'attente du chargement de toutes les vidéos j'ai utilisé `Promise.all()`. Je n'ai pas testé le code, à voir :
function preloadVideos() {
    let allVideos = document.querySelectorAll('video');
    let videoPromises = [];

    allVideos.forEach((video) => {
        let url = video.getAttribute('data-src');

        let videoPromise = fetch(url)
            .then(response => {
                if (!response.ok) {
                    throw new Error('Erreur réseau');
                }
                return response.blob();
            })
            .then(videoBlob => {
                let vid = URL.createObjectURL(videoBlob);
                video.src = vid;
            })
            .catch(error => {
                console.log('Fetch error: ', error);
            });

        videoPromises.push(videoPromise);
    });

    Promise.all(videoPromises)
        .then(() => {
            allVideos.forEach((video) => {
                video.play();
            });
        })
        .catch(error => {
            console.log('Erreur lors du chargement des vidéos : ', error);
        });
}

document.addEventListener('DOMContentLoaded', () => {
    preloadVideos();
});

Modifié par Olivier C (25 May 2024 - 13:58)
Merci Olivier ,

cela a l'air de fonctionner, il faudra que je fasse des tests en ligne car forcement sur un serveur local c'est plutôt rapide, et les sources vidéos ne sont pas bien lourdes.

En fait c'est 2 fois la même vidéo dans une section plein écran, dont une avec un masque par dessus pour créer un effet. J'avais tout simplement au départ fait un clone de la première vidéo, mais l'autoplay ne fonctionnait pas sur Firefox sur la vidéo clonée, restriction du user agent, dommage car pas de pb sur webkit.

Je te tiens au jus Smiley cligne
Merci encore
De rien. Remercie ChatGPT : j'ai copié/collé ta fonction, lui ai demandé de la transcrire avec l'API fetch(), accompagné de ta demande initiale ; c'est lui qui m'a suggéré la solution `Promise.all`, je n'y avais pas pensé d’emblée mais savais que c'était pertinent car je l'utilise par ailleurs. C'est incroyable ce qu'on peut faire avec cet outils avec de bonnes directives (prompts).

Je veux bien que tu nous fasses un retour à l'occasion, ça m'intéresse.
Modifié par Olivier C (25 May 2024 - 22:20)
Ha, Chat GPT

J'évite de m'en servir car je préfère apprendre et comprendre ce que je fais.
C'est pas bien tu enfonces ce forum de partage de connaissance Smiley biggrin

J'avoue que c'est pratique effectivement... À utiliser à bon escient.

Je te tiens au courant quand j'aurai testé online et avec volontairement des vidéos lourdes.
Modifié par Pedrothelion (26 May 2024 - 12:52)
Bien sûr, il est important de savoir coder, ne serait-ce que pour savoir où on va et donner des instructions précises aux IA. J'ai un niveau intermédiaire en Javascript et je sais coder à la mano, d'ailleurs je continue à coder beaucoup à la mano. Je code depuis 2009. Mais pour autant j'utilise les outils d'aujourd'hui pour m'assister, pour faire du code review, écrire des tests, demander des suggestions d'amélioration, écrire le JSDoc pour les fonctions, etc.

Avec les IA j'ai régulièrement des déconvenues ou des choses que je n'arrive pas à faire mais - la vache - je me dis que si on en est là à l'instant zéro qu'est-ce que ça va être dans 5 ou 10 ans ?

On est d'accord : aujourd'hui il ne viendrait à l'idée à personne de poinçonner des cartes perforées ou de coder en binaire, ou même en assembleur. Il faut s'adapter.

Il me semblait important de te le dire car je vois que tu te diriges dans le monde professionnel du web et tu ne pourras y échapper. Le dev' qui ne se mettra pas aux IA est destiné à péricliter.

Modifié par Olivier C (26 May 2024 - 20:49)
Salut Olivier

bon après plusieurs tests, la meilleure solution à mon sens est la suivante : charger les videos avec un preload auto en paramètre sur la balise video, et déclencher la lecture en js. En tout cas c'est celle qui m'a donné le meilleur résultat en terme de synchro. J'ai fais des tests en chargeant volontairement une dizaine de vidéos sur une page, même si cela n'a pas de sens, et j'avais pas mal de décalage sur les dernières vidéos avec la solution de départ que l'on avait évoqué.


<video loop playsinline muted disablePictureInPicture preload="auto">
    <source src="./ma_video.mp4" type="video/mp4">
</video>
<video loop playsinline muted disablePictureInPicture preload="auto">
    <source src="./ma_video.mp4" type="video/mp4">
</video>



document.addEventListener("DOMContentLoaded", () => {
    
    const videos = document.querySelectorAll('video');
    let videosReady = 0;

    videos.forEach(video => {
        video.oncanplaythrough = () => {
            videosReady++;
            if (videosReady === videos.length) {
                videos.forEach(v => v.play());
            }
        };
    });

});

Modifié par Pedrothelion (30 May 2024 - 00:23)
Re Olivier,

bon en fait j'ai encore des décalages, et après d'autres recherches, voici 2 autres solutions, presque parfaites.

La première consiste à utiliser l'API captureStream(), c'est pour moi la meilleure solution, sauf qu'elle ne fonctionne sous aucun appareil iOS, et c'est bien dommage car la synchro est parfaite puisque c'est une capture en temps réel, donc aucun décalage.


<video loop playsinline muted disablePictureInPicture id="video1">
    <source src="./video.mp4" type="video/mp4">
</video>

<video loop playsinline muted autoplay disablePictureInPicture id="video2">
</video>		


const video1 = document.getElementById('video1');
const video2 = document.getElementById('video2');

video1.addEventListener('canplay', () => {
    let stream;
    const fps = 0;
    
    if (video1.captureStream) {
        stream = video1.captureStream(fps);
    } 
    else if (video1.mozCaptureStream) {
        stream = video1.mozCaptureStream(fps);
    } 
    else {
        console.error('Stream capture is not supported');
        stream = null;
    }
  
    video2.srcObject = stream;

    video1.play();
});
		



La deuxième consiste à utiliser un canvas, la synchro est également parfaite, sauf que ça lag un peu sous firefox, ce serait à priori un problème de refresh rate. Mais cela fonctionne sous tous les autres appareils, iOS compris.


<video loop playsinline muted disablePictureInPicture id="video1">
    <source src="./video.mp4" type="video/mp4">
</video>

<canvas id="video2" width="1920" height="1080"></canvas>


window.addEventListener("load", (event) => {

    let video1 = document.getElementById('video1');
    let canvas = document.getElementById('video2');
    let canvasContext = canvas.getContext('2d');

    video1.play();
            
    (function loop() {
        setTimeout(() => {
            canvasContext.drawImage(video1, 0, 0,1920,1080);
            loop();
        },20);
    })();
});



J'avais testé encore une autre solution en resynchronisant 2 vidéos via un setInterval, toutes les 500/800ms, en surveillant le currentTime de chaque vidéo, mais cela provoquait des lags.

Voilà je te partage tout ça car tu avais l'air intéressé Smiley smile
Modifié par Pedrothelion (01 Jun 2024 - 13:45)
Meilleure solution