Self video black screen when rendered in video-player-container with display:flex (2.2.5, not in 2.1.0)

Description
In the case of a layout with 2 video-player-containers (each stream user is rendered in one video-player-container), if the self video is rendered in a video-player-container that uses display: flex, this causes a black screen issue for the self video.

In version 2.1.0 this issue did not occur, but in version 2.2.5 it does. Could you clarify why this behavior changed and whether it is expected?

Browser Console Error
No error logs observed in the browser console.

Which Web Video SDK version?
Web Video SDK version**:** 2.2.5 (no issue in 2.1.0)

To Reproduce(If applicable)

  1. Attach the self video and another user’s video to two different video-player-containers.

  2. Detach both videos from their containers.

  3. Re-attach them, but swap their positions between the two video-player-containers.

Troubleshooting Routes

  • Before attaching the stream, clone the video-player-container element and replace the original one to force the DOM to re-render.

  • With this workaround, the self video can render correctly, but this should not be required in normal behavior.

Device (please complete the following information):

  • Device: Mostly observed on mobile devices (both iOS and Android).
  • OS: Android 15, IOS 18.6 …
  • Browser: Chorme, Safari
  • Browser Version : Chorme 119, 118 …

Additional context

  • If we keep using the workaround (cloning and replacing the video-player-container to force DOM re-render), will it have any side effects on performance or stability
  • Is this approach recommended or could it cause unexpected issues when working with the Web Video SDK?
  • Also, is there a more optimal or recommended method to handle this case instead of cloning/replacing the element?

Hey @Dungvl76

Thanks for your feedback.

I tested your approach and did not encounter the complete video blackout issue.
Here is my test code snippet:

<div class="container">
<video-player-container class="video1"></video-player-container>
<video-player-container class="video2"></video-player-container>
</div>
.container {
  display: flex;
  height: 80vh;
  width: 100vw;
  flex-direction: row;
}
video-player-container {
  flex-grow: 1;
  position: relative;
}
video-player{
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
// start video
async function startVideo(){
  await stream.startVideo();
  const {userId} = client.getSessionInfo();
  const element = await stream.attachVideo(userId, 3);
  document.querySelector('.video1').appendChild(element);
}
// remote video
function videoUserBeforeJoin(){
  client.getAllUser().forEach(async user => {
    if (user.bVideoOn) {
      const element = await stream.attachVideo(user.userId, 3);
      document.querySelector('.video2').appendChild(element);
    }
  });
}
 client.on('peer-video-state-change', async (payload) => {
    const { action, userId } = payload;
    if (action === 'Start') {
      const element = await stream.attachVideo(userId, 3);
      document.querySelector('.video2').appendChild(element);
    } else if (action === 'Stop') {
      const element = await stream.detachVideo(userId);
      if (Array.isArray(element)) {
        element.forEach(el => {
          el.remove();
        });
      } else {
        element.remove();
      }
    }
  });

  // switch video position
  async function swapVideoPosition(){
    const currentUserId = client.getSessionInfo().userId;
    const firstVideoUser = client.getAllUser().filter(user => user.bVideoOn && user.userId !== currentUserId)[0];
    // detach remote video
    if(firstVideoUser){
      const element = await stream.detachVideo(firstVideoUser.userId);
      if (Array.isArray(element)) {
        element.forEach(el => {
          el.remove();
        });
      } else {
        element.remove();
      }
    }
    // detach current video
     const element = await stream.detachVideo(currentUserId);
      if (Array.isArray(element)) {
        element.forEach(el => {
          el.remove();
        });
      } else {
        element.remove();
      }
      //attach remote video to video1 container
      const newElement = await stream.attachVideo(firstVideoUser.userId, 3);
      document.querySelector('.video1').appendChild(newElement);
      //attach current video to video2 container
      const newElement1 = await stream.attachVideo(currentUserId, 3);
      document.querySelector('.video2').appendChild(newElement1);
  }

I tested the swapVideoPosition method above and did not experience the self video
black screen issue. There is only one known issue: there’s a brief moment when the
video-player-container element appears black between detachVideo and attachVideo
calls, but as long as video rendering occurs, the element displays normally. We
will fix this in the upcoming release (2.2.10).

Thanks
Vic

Hi @vic.yang

Thanks for your answer


In the <video-player> component, setting position: static causes a display issue when following the To Reproduce steps. When I replace it with position: relative, the issue disappears, but if there are multiple <video-player> elements in the same video-player-container, their positions are incorrect for one frame. In VideoSDK version 2.1.0, I did not encounter this issue. Could you explain the difference between these two approaches and suggest a solution?

Thanks for your support!

Hi @Dungvl76

if there are multiple <video-player> elements in the same video-player-container , their positions are incorrect for one frame.

It depends on how you style the video-player element. Just treat it as a regular HTML element—you can use flex or grid layout to arrange them.

Could you explain the difference between these two approaches and suggest a solution?

I’m not sure which two approaches you want to compare. If it’s one video-player per video-player-container versus placing multiple video-players under the same video-player-container, we still recommend the latter because it offers better performance. However, it also depends on your actual use case.

Thanks
Vic

Hi @vic.yang

When I set position: static on the element, after detaching and re-attaching the self video within the same video-player-container (and the container only contains the self video), the self video appears black until I manually resize the browser. Could you explain why this happens and what causes the video not to render correctly in this scenario?

Thanks for your support!

Hi @Dungvl76

set position: static on the <video-player> element

This might be a styling issue and needs to be checked on your side. See if the video-player element has the correct size.

I modified the example above, changing the video-player style from position: 'absolute' to position: 'static', and it still works fine.

Thanks
Vic

Hi @vic.yang
I am experiencing an issue with video-player-container:

  • When attaching a remote video to a video-player-container, the SDK creates a shadow-root that contains a <canvas> element to render the video.

  • When attaching a local (start) video, the SDK also creates a shadow-root but without a <canvas>.

The problem occurs when I swap the remote video and the local video:

  • The <canvas> created from the previous remote video is not removed and still remains inside the shadow-root.

  • As a result, this <canvas> overlays the local video, causing the local video to appear black.

I confirmed this behavior in DevTools.

Could you please clarify if this is a bug in the SDK, and is there a way to ensure that the old <canvas> is properly cleaned up when swapping containers?
Thanks for your support!

Hi @Dungvl76

The problem occurs when I swap the remote video and the local video:

  • The <canvas> created from the previous remote video is not removed and still remains inside the shadow-root.
  • As a result, this <canvas> overlays the local video, causing the local video to appear black.

I tested the same scenario and indeed found a canvas element inside the shadow-root of the swapped video-player-container. However, the actual rendering still uses the video tag, and since it comes after the canvas element, there is no overlay issue.

Thanks
Vic

Hi @vic.yang

I have an additional finding with screenshots for reference:

After swapping containers, when the local video is blacked out, I noticed that if I manually change the width of the <canvas> inside the shadow-root, the local video becomes partially visible again. The visible area corresponds to the reduced pixel width of the <canvas>.

This suggests that the leftover <canvas> from the remote video render is not removed and continues to overlay on top of the local video, blocking it.

This issue occurs if the video-player element has position: static.

Hey @Dungvl76

Thanks for your feedback.

I applied your styles and also observed the black screen issue. Through debugging, I found that it was caused by the aspect-ratio property. After removing this property, it worked normally.

Thanks
Vic

Hi @vic.yang
Currently, removing the aspect-ratio property does not solve the issue; when starting a video, the shadow DOM still overlays the content. I can only fix it by changing the position of the video-player element from static to relative, but this causes the subsequent video-player elements added to the video-player-container to briefly render in the wrong position for a few frames. Do you have any other suggestions for fixing the shadow DOM overlay problem?
Thanks for your support!

Hi @Dungvl76

Sorry, I’m not sure how to fix this issue in your scenario, but if, as you mentioned, using relative positioning can resolve it, then that should work.

Thanks
Vic

Thanks Vic,
I will pass this on to the team and get back to you if we still have issues.

All the best,
Dung