0

There is a problem with following code.

async function loadVideo (src, crossOrigin)
{
   let video  = makeVideo (src, crossOrigin);

   return new Promise((resolve, reject) =>
   {
       Promise.allSettled ([
            new Promise ( (resolve, reject) => {video.addEventListener('playing',     () => {resolve(1);}, true )}   ),
            new Promise ( (resolve, reject) => {video.addEventListener('timeupdate',  () => {resolve(1);}, true )}   )   ])
            .then((values) => 
       {
         resolve(video);
       });
   });
}

I use the code for creating webgl2 textures. The problem is that event callbacks for playing is called ever again when video begins, and timeupdate event is called permanently. It doesn't cause big nor visible issues, but I need them to be called only once. Is there a way to remove listeners after first invocation?

See full code in following snippet:

function makeVideo (src, crossOrigin)
{
   let video  = document.createElement("video");
   if (crossOrigin) video.crossOrigin  =  crossOrigin;
   video.src         =  src;
   video.autoplay    = true;
   video.playsInline = true;
   video.muted       = true;
   video.loop        = true;
   video.play();
   return video;
}

async function loadVideo (src, crossOrigin)
{
   let video  = makeVideo (src, crossOrigin);
   video.width = 300;
   return new Promise((resolve, reject) =>
   {
       Promise.all ([
            new Promise ( (resolve, reject) => {video.addEventListener('playing',     () => {resolve(1);}, true )}   ),
            new Promise ( (resolve, reject) => {video.addEventListener('timeupdate',  () => {resolve(1);}, true )}   )   ])
            .then((values) => 
       {
         resolve(video);
       });
   });
}
document.addEventListener('DOMContentLoaded', async () => 
{
   loadVideo ("https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4")
      .then(vd => {document.body.appendChild(vd)});
});
armagedescu
  • 1,758
  • 2
  • 20
  • 31
  • Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi Jun 30 '23 at 22:30
  • "*I need them to be called only once.*" - why? What is the issue it causes? (I can see one, but I'm not sure it's the same you're thinking about) – Bergi Jun 30 '23 at 22:32
  • @Bergi When tell to avoid constructor antipattern, provide an exact way to avoid it when handling event listener. Note, this is the exact way to use it when transforming event listener into a promise https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise – armagedescu Jun 30 '23 at 22:40
  • @Bergi And about **why?**.... Seriously? Something being called continuously for no reason.... Doesn't it sound reasonable to not want it to happend – armagedescu Jun 30 '23 at 22:50
  • 1
    I'm not talking about the two `new Promise` calls that wrap the `addEventListener` each. I'm talking about the outer one, which is unnecessary - just `return Promise.all(…).then(() => video)`, or even better `await Promise.all(…); return video;` since you already use `async`. – Bergi Jul 01 '23 at 04:14
  • Ah ok you just want to avoid unnecessary calls to `resolve`, it was not clear to me whether you were talking about that or something else, "*there is a problem*" was too vague for me. Yes, to solve that, use the `once: true` option. – Bergi Jul 01 '23 at 04:16
  • @Bergi ```or even better await``` Thanks for that. That's something I've overlooked. – armagedescu Jul 02 '23 at 14:48

2 Answers2

2

You can use the once option.

Here is how:

async function loadVideo (src, crossOrigin)
{
   let video  = makeVideo (src, crossOrigin);

   return new Promise((resolve, reject) =>
   {
       Promise.allSettled ([
            new Promise ( (resolve, reject) => {video.addEventListener('playing',     () => {resolve(1);}, { once: true } )}   ),
            new Promise ( (resolve, reject) => {video.addEventListener('timeupdate',  () => {resolve(1);}, { once: true } )}   )   ])
            .then((values) => 
       {
         resolve(video);
       });
   });
}
Titus
  • 22,031
  • 1
  • 23
  • 33
1

You can unsubscribe right before ending the promise:

function makeVideo(src, crossOrigin) {
  let video = document.createElement('video');
  if (crossOrigin) video.crossOrigin = crossOrigin;
  video.src = src;
  video.autoplay = true;
  video.playsInline = true;
  video.muted = true;
  video.loop = true;
  video.play();
  return video;
}

async function loadVideo(src, crossOrigin) {
  console.log('loadVideo');
  let video = makeVideo(src, crossOrigin);
  video.width = 300;
  return new Promise((resolve, reject) => {
    Promise.all([
      new Promise((resolve, reject) => {
        const playingCallback = () => {
          console.log('playing');
          video.removeEventListener('playing', playingCallback, true);
          resolve(1);
        };
        video.addEventListener('playing', playingCallback, true);
      }),
      new Promise((resolve, reject) => {
        const timeupdateCallback = () => {
          console.log('timeupdate');
          video.removeEventListener('timeupdate', timeupdateCallback, true);
          resolve(1);
        };
        video.addEventListener('timeupdate', timeupdateCallback, true);
      }),
    ]).then((values) => {
      resolve(video);
    });
  });
}

document.addEventListener('DOMContentLoaded', async () => {
  loadVideo(
    'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4'
  ).then((vd) => {
    document.body.appendChild(vd);
  });
});
Sergio Tx
  • 3,706
  • 1
  • 16
  • 29