WaveSurfer.js and audio.play() both trigger playback — how to avoid duplicate sound and multiple waves playing?

I’m currently learning JavaScript and trying to build a simple SoundCloud clone. I’m using WaveSurfer.js for audio waveform visualization, but I’ve run into a couple of issues:

Problem 1: Double playback

Originally, I used a single element with audio.play() to control playback. After integrating WaveSurfer, when I click “play”, the track plays twice — once via audio.play() and once via WaveSurfer’s internal playback.
Should I fully replace my audio logic with WaveSurfer’s API (e.g. .play() / .pause()), or is there a way to keep my custom audio element and just use WaveSurfer for visualization?

Problem 2: All waveforms play together

On my profile page, I list multiple tracks (e.g. 3 songs), each with their own waveform.
I initialize WaveSurfer like this:

const ws = WaveSurfer.create({
  container: el,
  backend: 'MediaElement',
  media: audio,
  waveColor: '#ccc',
  progressColor: '#ff5500',
  height: 60,
  responsive: true,
  barWidth: 2,
});

But when I click to play one song, all waveforms start playing at the same time.
I suspect it’s because they’re all linked to the same element. How can I isolate playback so that only one waveform plays when clicked?

I’m using JavaScript-based page preloading (like PJAX) to avoid full reloads and keep music playing across pages. It works great — the audio continues — but the waveform visualization disappears after switching pages.

  function loadPage(event, url) {
    event?.preventDefault();
    fetch(url)
      .then(res => res.text())
      .then(html => {
        const doc     = new DOMParser().parseFromString(html, 'text/html');
        const content = doc.getElementById('main-content');

        document.getElementById('main-content').innerHTML = content.innerHTML;
        document.body.className                          = doc.body.className;
        document.querySelector('.parent').className      = doc.querySelector('.parent').className;
        window.history.pushState({}, '', url);

        initMusic();  
      });
  }

  window.addEventListener('popstate', () =>
    loadPage(null, location.pathname)
  );

Also im using only one audio element in base.html and example of wavesurfer.js in my profile.html

<div class="waveform" data-src="{{ item.audio_file.url }}" id="waveform-{{ forloop.counter }}"></div>

document.addEventListener('DOMContentLoaded', () => {
  const waveformElements = document.querySelectorAll('.waveform');

  if (waveformElements.length === 0) return;

  const audio = document.getElementById('player');

  waveformElements.forEach((el) => {
    const src = el.dataset.src;

    const ws = WaveSurfer.create({
      container: el,
      backend: 'MediaElement',
      media: audio,
      waveColor: '#ccc',
      progressColor: '#ff5500',
      height: 60,
      responsive: true,
      barWidth: 2,
    });

    ws.load(src);
    window.wavesurfers = window.wavesurfers || {};
    window.wavesurfers[src] = ws;
  });
});

Any advice or best practices would be greatly appreciated!

I tried initializing WaveSurfer with the same element using the MediaElement backend. I expected it to display the waveform and control playback for each track individually. I also tried preloading pages using JavaScript to keep the music playing across pages.
However, when I play one track, all waveforms start playing at once. Also, after switching pages, the waveform disappears even though the audio keeps playing.

Trying to use querySelector in JavaScript for a button

I am creating a quiz application for a shcool project, but I’m having trouble with just this one part:

checkAnswerButton.addEventListener('click', () => 
{
    const currentQuestion = questions[currentQuestionIndex];
    const selectedOption = optionsElement.querySelector('.selected');
    if (selectedOption) {
        const selectedAnswer = selectedOption.textContent;
        selectAnswer(selectedAnswer);
        };
    }
);

I am trying to make it so that when this button is pressed, it will highlight the correct answer. However, it’s currently not working and I am not sure why.

Node js run time of js?

I am new to node.js and while starting i learn that Nodejs is run time environment of Java script now, what actually is run time? what is environment? these words like environment, runtime ,dependencies, utilities these words are very common and frequently used and heard without knowing what actually these are ?

div scrolling with the page

I’m working on a writing sprint tool in HTML/CSS. It’s a timed writing session where the user selects a duration, hits “Start”, and writes until the timer ends. The issue, however, lies with the positioning, and not with the tool.

The layout has three main parts.

  • Left: #SPRINT-SETTINGS
    A settings panel with dropdowns to choose a sprint duration, themes, and a start button.

  • Right: #SPRINT-CARD
    A large sprint card that displays a background image (selected from
    the settings panel.), and a large countdown timer in the center.

Even though my #sprint-card is styled with position: fixed and has top and right values, it still moves down when I scroll down. I expected it to stay pinned in place like everything else.

Things I’ve checked and one:

  1. #sprint-card is absolutely using position: fixed
  2. No JavaScript is modifying its position
  3. The parent container uses display: flex (.sprint-layout)
  4. Other fixed elements like the .sprint-settings panel don’t have this issue,
    only the card does

Any ideas on why this is happening or what else I should check?

Thanks in advance!

HTML:

<main class="sprint-page">
    <h1>Writing Sprint</h1>
    <p>Customize your sprint session below and press start when ready. You can adjust some settings during the sprint!</p>
    <quote class="attribution">All background images were pulled from Freepik. They belong to the rightful owner.</quote>
</main>

  <div class="sprint-layout">
    <section class="sprint-settings">
    <label for="duration">
      Sprint Duration
      <span class="tooltip" data-tooltip="Select the total sprint time.">?</span>
    </label>
      <select id="duration">
        <option value="10">10 Minutes</option>
        <option value="20">20 Minutes</option>
        <option value="30">30 Minutes</option>
        <option value="45">45 Minutes</option>
        <option value="60">60 Minutes</option>
      </select>

      <label for="background">
        Background Image
        <span class="tooltip" data-tooltip="Select a background to set the mood while you write.">?</span>
      </label>
      <select id="background">
        <option value="Images/sprints/dragonknight.jpg">Knight and Dragons</option>
        <option value="Images/sprints/dragoncastle.jpg">Knight and Castle</option>
        <option value="Images/sprints/castlecliff.jpg">Castle on Cliff</option>
        <option value="Images/sprints/ancientruins.jpg">Ancient Ruins</option>
        <option value="Images/sprints/sunkencity.jpg" selected>Sunken City</option>
        <option value="Images/sprints/finalbattle.jpg">Final Battle</option>
      </select>

      <label for="ambience">
        Ambience
        <span class="tooltip" data-tooltip="Choose background ambience for your sprint session.">?</span>
      </label>
      <select id="ambience">
        <option value="none">None</option>
        <option value="Sound/Forest.mp3">Forest</option>
        <option value="Sound/Night.mp3">Night</option>
        <option value="Sound/Windhowl.mp3">Wind Howling</option>
        <option value="Sound/Fireplace.mp3">Fireplace</option>
        <option value="Sound/Birds.mp3">Birds</option>
      </select>

      
      <label for="volume-slider">
        Volume
        <span class="tooltip" data-tooltip="Adjust the volume of the selected ambience audio.">?</span>
      </label>
      <input type="range" id="volume-slider" min="0" max="100" value="50" />

      <button id="start-btn" class="btn" onclick="toggleSprint()">Start Sprint</button>
      <button class="btn" onclick="resetSprint()">Reset Sprint</button>
    </section>

    <section class="sprint-card" id="sprint-card">
      <div class="sprint-info">
        <h2 id="sprint-status">Ready to Sprint!</h2>
        <p id="sprint-timer">00:00</p>
        <div id="motivation-text" class="motivation-text">You’ve got this!</div>
      </div>
    </section>
  </div>

<footer>
  <div class="footer-container">
    <div class="footer-column">
      <h4>About</h4>
      <a href="about.html" aria-label="About FantasyWriters">About Us</a>
      <a href="contact.html" aria-label="Contact FantasyWriters">Contact Us</a>
      <a href="contact.html" aria-label="Partnerships with FantasyWriters">Partnerships</a>
      <a href="faq.html" aria-label="Frequently Asked Questions">Frequently Asked Questions</a>
    </div>
    <div class="footer-column">
      <h4>Resources</h4>
      <a href="writingsprint.html" aria-label="Writing Sprint Tool">Writing Sprint</a>
      <a href="namegenerator.html" aria-label="Name Generator Tool">Name Generator</a>
    </div>
    <div class="footer-column">
      <h4>Community</h4>
      <a href="https://discord.com/invite/7nu2Zz8StN" target="_blank" rel="noopener" aria-label="Join our Discord community">
        <img src="Images/Discord_Logo.png" alt="Discord logo" class="footer-icon" /> Discord
      </a>
      <a href="https://www.reddit.com/r/fantasywriters/" target="_blank" rel="noopener" aria-label="Join our Reddit community">
        <img src="Images/Reddit_Logo.png" alt="Reddit logo" class="footer-icon" /> Reddit
      </a>
    </div>
  </div>
  <div class="footer-copy">
    © 2025 FantasyWriters. All rights reserved.
  </div>
</footer>

</body>
</html>

CSS:

body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background-color: white;
  color: #333;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
  padding: 3rem 1rem;
  max-width: 700px;
  margin: 0 auto;
  text-align: center;
}

.sprint-page {
  padding: 1rem 2rem;
  text-align: left;
  width: 20%;
  margin: 0;
  position: relative;
  top: 0;
  z-index: 5;
  background-color: #e2e1e1;
  box-shadow: rgba(85, 84, 85, 0.041) 3px 0px 0px;
}

.sprint-layout {
  display: flex;
  justify-content: space-between;
  width: 100%;
  padding: 0 2rem;
  box-sizing: border-box;
  background-color: rgb(21, 255, 0);
}

.sprint-settings {
  position: fixed;
  left: 2rem;
  top: 250px;
  width: 250px;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  z-index: 10;
}

.sprint-card {
  position: fixed;
  right: 3rem;
  top: 150px;
  width: 1300px;
  height: 600px;
  background-image: url("../Images/sprints/dragonknight.jpg");
  background-size: cover;
  background-position: center;
  color: white;
  padding: 2rem;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 0;
}

.sprint-info {
  background-color: rgba(0, 0, 0, 0.6);
  padding: 2rem;
  border-radius: 10px;
  text-align: center;
  width: 50%;
}

.sprint-info h2 {
  margin: 0;
  font-size: 2.5rem;
}

#sprint-timer {
  font-size: 4rem;
  margin-top: 1rem;
  font-weight: bold;
}

.sprint-settings label {
  font-weight: bold;
  color: #333;
}

.sprint-settings select, 
.sprint-settings button {
  padding: 0.5rem;
  font-size: 1rem;
  border-radius: 5px;
  border: 1px solid #ccc;
  width: 100%;
  box-sizing: border-box;
}

.sprint-settings button.btn {
  margin-top: 1rem;
  cursor: pointer;
  background-color: #77946e;
  color: white;
  border: none;
}

.sprint-settings button.btn:hover {
  background-color: #45a049;
}

.attribution {
  display: block;
  font-style: italic;
  color: #555;
  background-color: #ffffff5d;
  padding: 10px 20px;
  margin: 600px 0;
  font-size: 0.9em;
}

.motivation-text {
  text-align: center;
  margin-top: auto;
  font-size: 1.1rem;
  font-weight: bold;
  color: #ffffff;
  opacity: 0;
  transition: opacity 1s ease-in-out;
}

@keyframes glow {
  0%, 100% { text-shadow: 0 0 10px #ffd700; }
  50% { text-shadow: 0 0 20px #ff4500; }
}

.sprint-complete {
  font-size: 2rem;
  color: #ffd700;
  animation: glow 1.5s infinite;
  text-align: center;
  margin-top: 1rem;
}

@media (max-width: 768px) {
  .sprint-page {
    width: 30%;
    padding: 0.5rem;
  }

  .sprint-page h1 {
    font-size: 20px;
  }

  .sprint-page p {
    font-size: 14px;
  }

  .sprint-layout {
    display: flex;
    flex-direction: row;
    overflow-x: auto;
    padding: 0 1rem;
  }

  .sprint-settings {
    position: fixed;
    top: 240px;
    left: 0.1rem;
    width: 200px;
    gap: 0.8rem;
    padding: 0.5rem;
    font-size: 0.9rem;
    z-index: 10;
  }

  .sprint-card {
    position: fixed;
    top: 200px;
    right: 0.4rem;
    width: 480px;
    height: 300px;
    padding: 1rem;
    border-radius: 8px;
    background-size: cover;
    background-position: center;
    color: white;
  }

  .sprint-info {
    width: 100%;
    padding: 1rem;
    font-size: 0.9rem;
  }

  .sprint-info h2 {
    font-size: 1.5rem;
  }

  #sprint-timer {
    font-size: 2rem;
  }

  .motivation-text {
    font-size: 0.9rem;
  }
}

.tooltip {
  display: inline-block;
  margin-left: 5px;
  color: #414141;
  background-color: #c7baba;
  border-radius: 50%;
  width: 18px;
  height: 18px;
  text-align: center;
  font-size: 12px;
  line-height: 18px;
  font-weight: bold;
  cursor: pointer;
  position: relative;
  z-index: 1000;
}

.tooltip:hover::after {
  content: attr(data-tooltip);
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 125%;
  background-color: #333;
  color: #fff;
  padding: 6px 10px;
  border-radius: 5px;
  white-space: normal;
  width: 200px;
  font-size: 0.85rem;
  z-index: 1000;
  opacity: 1;
  transition: opacity 0.3s;
}

.tooltip:hover::before {
  content: "";
  position: absolute;
  bottom: 110%;
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: #333;
  z-index: 1001;
}

googles direct dynamic script loading no longer works for Google’s new auto complete

I am migrating to googles new places Auto complete. I am trying to load the script using Google recommended method for loading. They says there are 3 ways to load the map

  1. Use dynamic library import.

  2. Use the direct script loading tag.

  3. NPM js-api-loader package.

I am focused on the first two.

I followed the guide and was able to load the script with dynamic library import (option 1).
However the 2nd option: direct script loading tag does not works. It used to work under the legacy script (not sure if its been ported over to the new places API).

I need to use option two because I want to dynamically load the script only when its being used.

This is example of script for option 1 – dynamic library import:

<script>
  (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
    key: "YOUR_API_KEY",
    v: "weekly",
    // Use the 'v' parameter to indicate the version to use (weekly, beta, alpha, etc.).
    // Add other bootstrap parameters as needed, using camel case.
  });
</script> 

this is example of option 2 Direct loading:

<script async
    src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&loading=async&libraries=places&callback=initMap">
</script>

And this is full example of how i was able to load using dynamic library import (option one):

HTML:

<!doctype html>
<html>
<head>
    <title>Place Autocomplete element</title>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
</head>
<body>
<div class="place-autocomplete-card" id="place-autocomplete-card">
    <p>Search for a place here:</p>
</div>

<script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
({key: "MYKEY", v: "weekly"});</script>

</body>
</html>

JS

async function initMap() {
    await google.maps.importLibrary("places");

    const country = 'uk'
 
    const placeAutocomplete = new google.maps.places.PlaceAutocompleteElement();
    const card = document.getElementById('place-autocomplete-card');
    card.appendChild(placeAutocomplete);

    document.body.appendChild(selectedPlaceInfo);
 
    placeAutocomplete.addEventListener('gmp-select', async ({ placePrediction }) => {
        const place = placePrediction.toPlace();

        console.log('places return', place);
        await place.fetchFields({ fields: ['displayName', 'formattedAddress',    'location','addressComponents'] });
        
    });

}
initMap();

when I used the exact same script above but change the script to option two i.e

<script async
    src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&loading=async&libraries=places&callback=initMap">
</script>

I got the following error messages:

 - Uncaught (in promise) ReferenceError: google is not defined
   
 - Uncaught (in promise) InvalidValueError: initMap is not a function

WordPress Taxonomy check box to radio button

In wordpress I am recently working on the custom post type with a custom taxonomy for the post type. In the post edit page i need to convert my taxonomy check box to radio button because the user need to select only one term for one post and i am using gutenberg editor. Help me to figure out this problem.

Does JavaScript (V8) optimize concatenation of const strings like Java does?

I’m learning about string concatenation in JavaScript and how it compares to Java.

In Java, if you write:

final String LET = "a";
final String OOO = "aaaaa";
final String LET_OOO = LET + OOO;

The compiler combines these constant strings at compile time, so NO new string is created at runtime.

Can JavaScript do the same? For example:

const a = "a";
const b = "bbb";
const c = a + b;

I expected that since both are constants, JavaScript might optimize and not create a new string in memory—similar to how Java combines final strings at compile time.

Does JavaScript (like V8) concatenate these without creating a new string in memory? Or does it always create a new string?

Ant Design Dropdown with contextMenu trigger closes instantly when hovering — how to keep it open?

I’m using Ant Design’s Dropdown component in a React app. The dropdown is triggered via right-click using trigger={[“contextMenu”]}.
However, there’s an issue:

When I right-click on the element, the dropdown appears — but as soon as I try to move my mouse toward the menu to click “Start”, “Logs”, or “Delete”, the menu disappears instantly.

What I’ve Tried:
Set a controlled open state using useState(false)

Used onOpenChange={(open) => setDropdownOpen(open)} along with open={dropdownOpen}

Moved my logic to set status inside onOpenChange when open === true

Added mouseLeaveDelay={0.3}

Used getPopupContainer={(triggerNode) => triggerNode.parentNode}

Ensured my menu={{ items }} is correct and stable

Wrapped the trigger element in a with a fixed height and width

But nothing worked — the dropdown still vanishes as soon as I try to hover over the menu options.

`

const [dropdownOpen, setDropdownOpen] = useState(false);

const items = [
  {
    key: "start",
    label: "Start",
  },
  {
    key: "logs",
    label: "Logs",
  },
  {
    key: "delete",
    label: "Delete",
  },
];

<Dropdown
  open={dropdownOpen}
  onOpenChange={(open) => {
    setDropdownOpen(open);
    if (open && node?.id) {
      // setting status based on componentData
    }
  }}
  menu={{ items }}
  trigger={["contextMenu"]}
  mouseLeaveDelay={0.3}
  getPopupContainer={(trigger) => trigger.parentNode}
>
  <div style={{ width: 150, height: 100, background: "#f0f0f0" }}>
    Right-click me
  </div>
</Dropdown>

`

MediaStream metadata not being loaded in react

Im making a mutli user video chatting app using webrtc but the remote streams that are recieved with the peer connections dont play .
this is my peerManager class :

import { v4 as uuidv4 } from "uuid";

export class PeerService {
  private ws: WebSocket;
  private peerList: Map<string, RTCPeerConnection>;
  private localStream: MediaStream | null = null;
  public remoteStreams: Map<string, MediaStream> = new Map();

  constructor(soc: WebSocket, localStream?: MediaStream) {
    this.peerList = new Map<string, RTCPeerConnection>();
    this.ws = soc;
    this.localStream = localStream || null;
  }

  // Add local stream to be shared with peers
  async addLocalStream(stream: MediaStream) {
    console.log("local stream");
    this.localStream = stream;

    // Add tracks to all existing peer connections
    this.peerList.forEach((pc) => {
      stream.getTracks().forEach((track) => {
        pc.addTrack(track, stream);
      });
    });
  }

  // Remove local stream
  removeLocalStream() {
    if (this.localStream) {
      this.peerList.forEach((pc) => {
        const senders = pc.getSenders();
        senders.forEach((sender) => {
          if (
            sender.track &&
            this.localStream?.getTracks().includes(sender.track)
          ) {
            pc.removeTrack(sender);
          }
        });
      });
      this.localStream = null;
    }
  }

  // Get remote stream for a specific peer
  getRemoteStream(peerID: string): MediaStream | null {
    return this.remoteStreams.get(peerID) || null;
  }

  // Get all remote streams
  getAllRemoteStreams(): Map<string, MediaStream> {
    return this.remoteStreams;
  }

  private setupTrackHandlers(pc: RTCPeerConnection, peerID: string) {
    // Handle incoming remote tracks
    pc.ontrack = (event: RTCTrackEvent) => {
      console.log("tracks adde");
      const remoteStream = event.streams[0];
      console.log(event.streams);
      if (remoteStream) {
        this.remoteStreams.set(peerID, remoteStream);
        // console.log(this.remoteStreams)
        window.dispatchEvent(
          new CustomEvent("remoteStreamAdded", {
            detail: { peerID, stream: remoteStream },
          })
        );
      }
    };

    if (this.localStream) {
      console.log("localllllllllll");
      this.localStream.getTracks().forEach((track) => {
        pc.addTrack(track, this.localStream!);
      });
    }
  }

  async addPeer() {
    console.log(
      "adddd poeererererewrawgdsfg hdsfg jsfoghsdfogndsofngdsangakjb"
    );
    const peerID = uuidv4();
    
    const pc = new RTCPeerConnection();
    console.log("add peer");
    this.setupTrackHandlers(pc, peerID);

    pc.onicecandidate = (event: any) => {
      //   console.log("ws in PEER MANAGER", this.ws);
      if (event.candidate && this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(
          JSON.stringify({
            type: "ice-candidate",
            payload: {
              candidate: event.candidate,
              peerID,
            },
          })
        );
      }
    };

    let offer = await pc.createOffer();
    await pc.setLocalDescription(new RTCSessionDescription(offer));
    console.log(pc.signalingState);
    //  console.log(`during offer peer :${peerID}`,pc)

    if (this.ws) {
      this.ws.send(
        JSON.stringify({
          type: "offer",
          payload: {
            peerID,
            sdp: offer,
          },
        })
      );
    }

    this.peerList.set(peerID, pc);
  }

  async handleSignal(message: any) {
    console.log(message);
    console.log(this.peerList);
    let message_type = message.type;
    /*   let pc;
    if (message.type !== "offer"){
      console.log(message)
      pc = this.peerList.get(message.payload.peerID);} */
    //console.log(pc );
    //console.log(message)
    /*  if (!pc) {
      console.log("peer connection not found ");
      return;
    } */

    console.log(message_type);
    switch (message_type) {
      case "offer": {
        const peerID = message.payload.peerID;
        const peerConnection = new RTCPeerConnection();

        // Optional: Monitor ICE state
        peerConnection.oniceconnectionstatechange = () => {
          console.log(
            "ICE state for",
            peerID,
            "→",
            peerConnection.iceConnectionState
          );
        };

        // ✅ Add local tracks BEFORE setting remote description
        this.setupTrackHandlers(peerConnection, peerID); // This must add tracks if localStream exists

        // ✅ Set remote description
        await peerConnection.setRemoteDescription(
          new RTCSessionDescription(message.payload.sdp)
        );

        // ✅ Store the peer connection immediately under the correct key
        this.peerList.set(peerID, peerConnection);

        // ✅ Create and send answer
        const answer = await peerConnection.createAnswer();
        await peerConnection.setLocalDescription(answer);

        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
          this.ws.send(
            JSON.stringify({
              type: "answer",
              payload: {
                peerID,
                sdp: answer,
              },
            })
          );
        }

        break;
      }

      case "answer":
        console.log("answer");
        const pc = this.peerList.get(message.payload.peerID);
        console.log(pc);
        if (!pc) return;
        // if(pc.connectionState === )
        // console.log(pc.signalingState);
        // console.log(`during asnwer peer :${message.payload.peerID}`,pc)

        await pc?.setRemoteDescription(
          new RTCSessionDescription(message.payload.sdp)
        );
        break;

      case "ice-candidate": {
        const pc = this.peerList.get(message.payload.peerID);
        if (!pc) return;
        await pc?.addIceCandidate(
          new RTCIceCandidate(message.payload.candidate)
        );
        break;
      }
    }
  }

  closePeer(peerID: string) {
    const pc = this.peerList.get(peerID);
    pc?.close();
    this.peerList.delete(peerID);

    this.remoteStreams.delete(peerID);

    window.dispatchEvent(
      new CustomEvent("remoteStreamRemoved", {
        detail: { peerID },
      })
    );
  }

  closeAll() {
    this.peerList.forEach((pc, peerId) => {
      pc.close();
    });
    this.peerList.clear();

    this.remoteStreams.clear();

    this.removeLocalStream();
  }
}

export default PeerService;

VidConference.tsx

import React, { useState, useEffect, useRef } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
  Mic,
  MicOff,
  Video,
  VideoOff,
  PhoneOff,
  Users,
  MessageSquare,
  Share,
  Settings,
} from "lucide-react";
import Button from "../ui/Button";
import { useMeetings } from "../../contexts/MeetingsContext";
import { useAuth } from "../../contexts/AuthContext";
import ParticipantGrid from "./ParticipantGrid";
import { Participant } from "../../types";
import { useSoc } from "../../hooks/usesoc";
import { PeerService } from "../../utils/peer";
const VideoConference: React.FC = () => {
  const { getCurrentMeeting, leaveMeeting } = useMeetings();
  const { user } = useAuth();
  const meeting = getCurrentMeeting();

  const [isMuted, setIsMuted] = useState(false);
  const [isVideoOn, setIsVideoOn] = useState(true);
  const [showParticipants, setShowParticipants] = useState(false);
  const [showChat, setShowChat] = useState(false);
  const [participants, setParticipants] = useState<Participant[]>([]);
  const localVid = useRef<HTMLVideoElement | null>(null);
  const [stat, setStat] = useState<boolean>(false);
  const remoteVid = useRef<HTMLVideoElement | null>(null);
  const { roomid } = useParams();
  const peerManager = useRef<PeerService | null>(null);
  useEffect(() => {
    const mockParticipants: Participant[] = [
      {
        id: user?.id || "1",
        name: user?.name || "You",
        email: user?.email || "",
        avatar: user?.avatar,
        isMuted: true,
        isVideoOn: true,
        isHost: true,
        mediaStream: localVid,
      },
    ];

    setParticipants(mockParticipants);
  }, [user]);

  const soc = useSoc();

  async function playVideoFromCamera() {
    try {
      const constraints: MediaStreamConstraints = {
        video: {
          width: { ideal: 1280 },
          height: { ideal: 720 },
          frameRate: { ideal: 30 },
          facingMode: "user",
        },
        audio: {
          echoCancellation: true,
          noiseSuppression: true,
          sampleRate: 44100,
        },
      };
      console.log("Requesting user media with constraints:", constraints);
      const stream = await navigator.mediaDevices.getUserMedia(constraints);
      console.log("Media stream obtained:", stream);
      console.log("Video tracks:", stream.getVideoTracks());
      console.log("Audio tracks:", stream.getAudioTracks());
      return stream;
    } catch (error) {
      console.error("Error opening video camera.", error);
      return null;
    }
  }

  function changeParticipants(s: Participant) {
    setParticipants((prev: any) => {
      console.log("remote stream", s.mediaStream);
      return [...prev, s];
    });
  }

  function populateRemoteStreams() {
    peerManager.current?.remoteStreams.forEach((stream, peerId) => {
      const alreadyExists = participants.some((p) => p.id === peerId);
      if (alreadyExists) return;
      console.log("pouplate");

      const newRef = React.createRef<HTMLVideoElement>();
     

      changeParticipants({
        id: peerId,
        name: "yo",
        avatar: "isudgfius",
        isMuted: true,
        isVideoOn: true,
        isHost: false,
        mediaStream: newRef,
        stream: stream,
      });
    });
  }
  // Set up video independently of WebSocket
  useEffect(() => {
    if (peerManager.current == null) {
      playVideoFromCamera().then(async (stream) => {
        if (stream && localVid.current !== null) {
          localVid.current.srcObject = stream;
          // Force the video to play
          localVid.current
            .play()
            .catch((e) => console.error("Error playing video:", e));
        }

        if (stream && stat) {
          peerManager.current = new PeerService(
            soc.current as WebSocket,
            stream
          );
          peerManager.current.addPeer().then(async () => {
           // await peerManager.current?.addLocalStream(stream as MediaStream);
          });
          // const stream = await playVideoFromCamera()
        }
      });
    }
  }, [stat]);
  useEffect(() => {
    let check = peerManager.current?.remoteStreams.size;
    function checkRemoteStreams() {
      if (peerManager.current) {
        // console.log("Remote streams:", peerManager.current.remoteStreams);
        if (check != peerManager.current?.remoteStreams.size) {
          populateRemoteStreams();
          check = peerManager.current.remoteStreams.size;
        }
      }
    }
    const intervalId = setInterval(checkRemoteStreams, 1000);

    return () => clearInterval(intervalId);
  }, []);
  useEffect(() => {
    if (soc.current)
      soc.current.onopen = () => {
        setStat(true);
      };
    if (!soc.current || soc.current.readyState !== WebSocket.OPEN) {
      return;
    }

    if (soc.current)
      soc.current.send(
        JSON.stringify({
          type: "create-room",
          room_id: roomid,
        })
      );
  }, [roomid, stat]);

  // WebSocket message handling
  useEffect(() => {
    if (!soc.current) return;
    const socket = soc.current;
    socket.onmessage = (m) => {
      if (peerManager.current)
        peerManager.current.handleSignal(JSON.parse(m.data));
    };
  }, [soc.current?.readyState, stat]);

 
  // Update local participant when video stream is available
  useEffect(() => {
    if (localVid.current && localVid.current.srcObject) {
      setParticipants((prev) =>
        prev.map((p) =>
          p.id === user?.id
            ? { ...p, isVideoOn: true, mediaStream: localVid }
            : p
        )
      );
    }
  }, [localVid.current?.srcObject, user?.id]);





  const handleLeaveMeeting = () => {
    leaveMeeting();
    // navigate("/dashboard");
  };

  const toggleMute = () => {
    setIsMuted(!isMuted);
    // Update participant state
    setParticipants((prev) =>
      prev.map((p) => (p.id === user?.id ? { ...p, isMuted: !isMuted } : p))
    );
  };

  const toggleVideo = () => {
    setIsVideoOn(!isVideoOn);
    // Update participant state
    setParticipants((prev) =>
      prev.map((p) => (p.id === user?.id ? { ...p, isVideoOn: !isVideoOn } : p))
    );
  };

  const handleShare = () => {
    // Copy meeting link to clipboard
    const meetingLink = `${window.location.origin}/video-conference/${roomid}`;
    navigator.clipboard.writeText(meetingLink).then(() => {
      // Could add a toast notification here
      console.log("Meeting link copied to clipboard");
    });
  };

  const handleSettings = () => {
    // Open settings modal or panel
    console.log("Settings clicked");
  };

  return (
    <div className="h-screen bg-gray-900 flex flex-col">
      {/* Header */}
      <header className="bg-gray-800/50 backdrop-blur-sm border-b border-gray-700 px-6 py-4">
        <div className="flex items-center justify-between">
          <div>
            <h1 className="text-lg font-semibold text-white">
              {meeting?.title || `Room: ${roomid}`}
            </h1>
            <div className="flex items-center space-x-4 text-sm text-gray-400">
              <span>Meeting ID: {meeting?.meetingCode || roomid}</span>
              <span>•</span>
              <span>{participants.length} participants</span>
            </div>
          </div>

          <div className="flex items-center space-x-2">
            <Button
              variant="outline"
              size="sm"
              onClick={() => setShowParticipants(!showParticipants)}
              leftIcon={<Users className="w-4 h-4" />}
            >
              <span className="hidden sm:inline">Participants</span>
              <span className="sm:hidden">{participants.length}</span>
            </Button>

            <Button
              variant="outline"
              size="sm"
              onClick={() => setShowChat(!showChat)}
              leftIcon={<MessageSquare className="w-4 h-4" />}
            >
              <span className="hidden sm:inline">Chat</span>
            </Button>
          </div>
        </div>
      </header>

      {/* Main Content */}
      <div className="flex-1 flex">
        {/* Video Grid */}
        <div className="flex-1 p-4">
          <ParticipantGrid participants={participants} />
        </div>

        {/* Sidebar */}
        {(showParticipants || showChat) && (
          <div className="w-80 bg-gray-800/50 backdrop-blur-sm border-l border-gray-700 p-4">
            {showParticipants && (
              <div className="mb-6">
                <h3 className="text-lg font-semibold text-white mb-4">
                  Participants ({participants.length})
                </h3>
                <div className="space-y-2">
                  {participants.map((participant) => (
                    <div
                      key={participant.id}
                      className="flex items-center space-x-3 p-2 rounded-lg hover:bg-gray-700/50"
                    >
                      {!participant.isVideoOn ? (
                        <img
                          src={
                            participant.avatar ||
                            `https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&w=50&h=50&dpr=2`
                          }
                          alt={participant.name}
                          className="w-8 h-8 rounded-full object-cover"
                        />
                      ) : (
                        <></>
                      )}
                      <div className="flex-1 min-w-0">
                        <p className="text-sm font-medium text-white truncate">
                          {participant.name}
                          {participant.isHost && (
                            <span className="ml-2 text-xs text-blue-400">
                              (Host)
                            </span>
                          )}
                        </p>
                      </div>
                      <div className="flex items-center space-x-1">
                        {participant.isMuted ? (
                          <MicOff className="w-4 h-4 text-red-400" />
                        ) : (
                          <Mic className="w-4 h-4 text-green-400" />
                        )}
                        {participant.isVideoOn ? (
                          <Video className="w-4 h-4 text-green-400" />
                        ) : (
                          <VideoOff className="w-4 h-4 text-red-400" />
                        )}
                      </div>
                    </div>
                  ))}
                </div>
              </div>
            )}

            {showChat && (
              <div>
                <h3 className="text-lg font-semibold text-white mb-4">Chat</h3>
                <div className="bg-gray-700/50 rounded-lg p-4 text-gray-400 text-sm text-center">
                  Chat feature coming soon...
                </div>
              </div>
            )}
          </div>
        )}
      </div>

      {/* Controls */}
      <div className="bg-gray-800/50 backdrop-blur-sm border-t border-gray-700 px-6 py-4">
        <div className="flex items-center justify-center space-x-4">
          <Button
            variant={isMuted ? "danger" : "secondary"}
            size="lg"
            onClick={toggleMute}
            className="w-12 h-12 rounded-full p-0 flex items-center justify-center"
            title={isMuted ? "Unmute" : "Mute"}
          >
            {isMuted ? (
              <MicOff className="w-5 h-5" />
            ) : (
              <Mic className="w-5 h-5" />
            )}
          </Button>

          <Button
            variant={!isVideoOn ? "danger" : "secondary"}
            size="lg"
            onClick={toggleVideo}
            className="w-12 h-12 rounded-full p-0 flex items-center justify-center"
            title={isVideoOn ? "Turn off camera" : "Turn on camera"}
          >
            {isVideoOn ? (
              <Video className="w-5 h-5" />
            ) : (
              <VideoOff className="w-5 h-5" />
            )}
          </Button>

          <Button
            variant="secondary"
            size="lg"
            onClick={handleShare}
            className="w-12 h-12 rounded-full p-0 flex items-center justify-center"
            title="Share meeting"
          >
            <Share className="w-5 h-5" />
          </Button>

          <Button
            variant="secondary"
            size="lg"
            onClick={handleSettings}
            className="w-12 h-12 rounded-full p-0 flex items-center justify-center"
            title="Settings"
          >
            <Settings className="w-5 h-5" />
          </Button>

          <Button
            variant="danger"
            size="lg"
            onClick={handleLeaveMeeting}
            className="w-12 h-12 rounded-full p-0 flex items-center justify-center"
            title="Leave meeting"
          >
            <PhoneOff className="w-5 h-5" />
          </Button>
        </div>
      </div>
    </div>
  );
};

export default VideoConference;

Participantgrid.tsx

import React, { useEffect, useRef } from "react";
import { Mic, MicOff, Video, VideoOff, Crown } from "lucide-react";
import { Participant } from "../../types";

interface ParticipantGridProps {
  participants: Participant[];
}

const ParticipantGrid: React.FC<ParticipantGridProps> = ({ participants }) => {
  const videoRefs = useRef<Record<string, HTMLVideoElement | null>>({});

  useEffect(() => {
    participants.forEach((participant) => {
      const videoEl = videoRefs.current[participant.id];

      if (
        participant.isVideoOn &&
        videoEl &&
        participant.stream &&
        participant.stream.getTracks().length > 0 &&
        videoEl.srcObject !== participant.stream
      ) {
        videoEl.srcObject = null;
        videoEl.srcObject = participant.stream;
        videoEl
          .play()
          .then(() => console.log("Playing:", participant.id))
          .catch((error) => {
            console.error("Error playing video for", participant.id, error);
          });
          videoEl.onloadedmetadata = () => {
            videoEl.play().catch((error) => {
              console.error("Error playing video for", participant.id, error);
            });
          };
      }
    });
  }, [participants]);

  const getGridCols = () => {
    const count = participants.length;
    if (count === 1) return "grid-cols-1";
    if (count === 2) return "grid-cols-2";
    if (count <= 4) return "grid-cols-2";
    if (count <= 6) return "grid-cols-3";
    return "grid-cols-4";
  };


  return (
    <div className={`grid ${getGridCols()} gap-4 h-full`}>
      {participants.map((participant) => (
        <div
          key={participant.id}
          className="relative bg-gray-800 rounded-lg overflow-hidden group"
        >
          {/* Video/Avatar */}
          <div className="w-full h-full flex items-center justify-center vid_area">
            {participant.isVideoOn && !participant.isHost ? (
              participant.mediaStream ? (
                <>
                  <video
                    key={participant.id}
                    ref={(el) => {
                      videoRefs.current[participant.id] = el;
                    }}
                    autoPlay
                    muted={participant.isMuted}
                    playsInline
                    className="w-full h-full object-cover transform scale-x-[-1]"
                    onLoadedMetadata={(e) => {
                      console.log("Loaded metadata for", participant.id);
                    }}
                    onError={(e) =>
                      console.error("Video error for", participant.id, e)
                    }
                  />
                  {/*  <canvas ref={canvasRef} className="..." /> */}
                </>
              ) : (
                <div className="w-full h-full bg-gradient-to-br from-blue-500/20 to-purple-500/20 flex items-center justify-center">
                  <div className="text-6xl font-bold text-white/20">
                    {participant.name?.charAt(0).toUpperCase()}
                  </div>
                </div>
              )
            ) : (
              <div className="flex flex-col items-center justify-center space-y-4">
                <img
                  src={
                    participant.avatar ||
                    `https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&w=200&h=200&dpr=2`
                  }
                  alt={participant.name}
                  className="w-20 h-20 rounded-full object-cover border-4 border-gray-600"
                />
                <div className="flex items-center space-x-2 px-3 py-1 bg-gray-700/80 rounded-full">
                  <VideoOff className="w-4 h-4 text-red-400" />
                  <span className="text-sm text-gray-300">Camera off</span>
                </div>
              </div>
            )}
          </div>

          {/* Participant Info */}
          <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4">
            <div className="flex items-center justify-between">
              <div className="flex items-center space-x-2">
                <span className="text-white font-medium text-sm">
                  {participant.name}
                </span>
                {participant.isHost && (
                  <Crown className="w-4 h-4 text-yellow-400" />
                )}
              </div>

              <div className="flex items-center space-x-1">
                <div
                  className={`p-1.5 rounded-full ${
                    participant.isMuted ? "bg-red-500" : "bg-green-500"
                  }`}
                >
                  {participant.isMuted ? (
                    <MicOff className="w-3 h-3 text-white" />
                  ) : (
                    <Mic className="w-3 h-3 text-white" />
                  )}
                </div>
              </div>
            </div>
          </div>

          {/* Speaking Indicator */}
          {!participant.isMuted && (
            <div className="absolute top-4 left-4 w-3 h-3 bg-green-400 rounded-full animate-pulse" />
          )}
        </div>
      ))}
    </div>
  );
};

export default ParticipantGrid;

when i try to tlog the remote streams they get logged but cant be played
when i checked the readystate of the stream was 0 and the network state was 2

Change iframe src attribute before iframe loads for consent management

I have a WordPress site that uses some plugin which can display iFrames.

Now I want to use a consent manager (Usercentrics, fyi) that requires to change the src attribute to data-src in order to prevent the iframe from loading before consent has been given. The plugin mentioned above cannot do this.

Now my first approach was to change the src attribute upon DOMContentLoaded using JavaScript, like this:

<script>
document.addEventListener("DOMContentLoaded", function(){
    var iframeSrcValue = document.getElementById("partnerIFrame").src;
    document.getElementById('partnerIFrame').removeAttribute("src");        
    document.getElementById('partnerIFrame').setAttribute("data-src", iframeSrcValue);
});
</script>

I would insert this script into the head tag of the WordPress site using a plugin.

However, that seems to be not working well, as the iframe starts loading for a fraction of a second, before the script takes effect – which is not nice and also doesn’t fullfill the requirement of a consent management solution to only load third party ressource after consent.

What would be the best approach to achieve this goal?

Any hints are appreciated!

webpack file doesnt refer node_modules present in the imported path , but refer for it its node_modules

i am webpacking a module from pathA , node_modules are ignored

i import the pathA index.js in pathB node index.js dynamically which has the required node_modules

but when i run pathB node index.js , i get the following error

“error”: “Cannot find module ‘@playwright/test’nRequire stack:n- D:PersonalpackAdistindex.jsn- D:Personalpackbindex.js”

pathA/

  • dist/index.js # Webpacked bundle (externals: Playwright)
  • src/index.ts
  • webpack.config.js # With externals config

pathB/

  • node_modules/ # Contains @playwright/test
  • index.js # Imports pathA/dist/index.js

I will attach my config

const path = require("path");
const nodeExternals = require("webpack-node-externals");

module.exports = {
  entry: "./src/index.ts",
  target: "node",
  output: {
    filename: "index.js",
    path: path.resolve(__dirname, "dist"),
    library: {
      name: 'test-api',
      type: 'umd',
    },
    clean: true,
  },
  mode: "development",
  resolve: {
    extensions: [".ts", ".js" ],
  },
  externals: [nodeExternals()],
  module: {
    rules: [
      {
        test: /.ts$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
      {
        test: /(LICENSE|.d.ts|.md|.apk|.png|.sh|.ps1|.dmg|.css|.html|.exe|.svg|.ttf)$/,
        use: ["null-loader"],
      },
      {
        test: /.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              [
                "@babel/preset-env",
                {
                  targets: { node: "current" },
                },
              ],
            ],
          },
        },
      },
    ],
  },
 
  devtool: "source-map",
};

How to mutate a nested object in the beforeChange hook in a collection?

I’m trying to mutate a nested object in the beforeChange hook of a Payload CMS collection definition:

const newContent = structuredClone(props.data.content);

const newData = await props.req.payload({...}) // making an internal call to get the new data

newContent.prop.prop.prop.prop.prop1 = newData.prop1
newContent.prop.prop.prop.prop.prop2 = newData.prop2

console.log(newContent)

return {
   ...props.data,
   content: newContent
}

When I execute console.log(newContent), I get the updated object, but when I console.log(data) in the afterChange hook, the unmodified object is printed. Moreover, I can see in MongoDB Compass that the document was not updated with my new object.

I’m using Payload v3. This is an oversimplified version of my code. The original code has 2 for of loops inside which the payload calls are made. However, I thought this should not impact the result, since the console.log at the end of beforeChange is displaying the correct data. What am I missing?

jsPDF adds image at incorrect size and position when using dimensions from getBoundingClientRect and clientHeight/clientWidth

I am trying to add an HTMLElement to a PDF after converting it to an image using "toPng" from "html-to-image".

The HTMLElement exists inside another, that plays the role of the page (used for visualizing the pages before rendering. set to the same dimensions as the pages for the PDF). I calculate the offsets of the image (of the component) using the distance between the bounding boxes of the element and it’s parent (the “page”):

    // page header is the element to render!
    const { parentElement } = pageHeader;
    if (!parentElement) {
      return;
    }
    const parentRect = parentElement.getBoundingClientRect();
    const headerRect = pageHeader.getBoundingClientRect();
    const xOffset = headerRect.left - parentRect.left;
    const yOffset = headerRect.top - parentRect.top;

For the image size i use clientHeight/clientWidth:

    const imgHeight = pageHeader.clientHeight;
    const imgWidth = pageHeader.clientWidth;

Here is how i create the image of the element and add it to the file:

    const headerImgData = await toPng(pageHeader as HTMLElement, {
      cacheBust: true,
      pixelRatio: 2,
    });

    pdf.addImage(headerImgData, 'PNG', xOffset, yOffset, imgWidth, imgHeight);

When creating the PDF object, i use ‘px’ as the unit:

    const pdf = new jsPDF({ orientation: 'p', unit: 'px', format: 'a4' });

The actual image rendered in the file is way bigger than expected, the same goes for the offsets.

I wanted the image to look the same way the HTMLElement looks in the DOM.

I tried logging the dimensions and they look fine (the same as in the DOM). I also tried other units and converting but the same the problem persists.

p-limit – Javascript modules ES6

On a webpage, I am not using Node.js.

I wanted to use p-limit like this:

<script src="
https://cdn.jsdelivr.net/npm/[email protected]/index.min.js
"></script>

But this doesn’t work.

Cannot use import statement outside a module

Naively, I tried:

<script type="module" src="
https://cdn.jsdelivr.net/npm/[email protected]/index.min.js
"></script>

Which errors out:

Failed to resolve module specifier "yocto-queue".

I eventually realized there are “commonJS” and “ES6”. So I tried:

<script type="module">
import pLimit from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'
</script>

Which doesn’t errors out until:

<script type="module">
    const l = pLimit(10);
</script>

where it says:

pLimit is not defined

Eventually, this works:

<script type="module">
import pLimit from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'
const l = pLimit(10);
</script>

I don’t understand why it needs to be in the same block.
If I modify the code to remove the import/export, I can use it in <script src="changed_plimit.js"></script> (of course, I don’t really want to do that).

Ultimately, I’d like to know if it can be used <script src="https://cdn..."></script>.
Obviously my understanding is limited. Am I completely off?