Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeError: Cannot read properties of null (reading 'src') #413

Open
YonatanAriel opened this issue Sep 28, 2024 · 5 comments
Open

TypeError: Cannot read properties of null (reading 'src') #413

YonatanAriel opened this issue Sep 28, 2024 · 5 comments

Comments

@YonatanAriel
Copy link

YonatanAriel commented Sep 28, 2024

it happens sometimes and crushes everything. It happens when I try to play a video.
I don't think that the src is in my code, I think it's the library code.
adding the error and my code (long and messy, sorry :( ) below.
I will appreciate any help, thanks in advance.

error - TypeError: Cannot read properties of null (reading 'src')
at X.sendMessage (www-widgetapi.js:203:95)
at Hb (www-widgetapi.js:195:100)
at X.setVolume (www-widgetapi.js:209:157)
at index.jsx:122:28
at commitHookEffectListMount (react-dom_client.js?v=2d8c18c0:16904:34)
at commitPassiveMountOnFiber (react-dom_client.js?v=2d8c18c0:18152:19)
at commitPassiveMountEffects_complete (react-dom_client.js?v=2d8c18c0:18125:17)
at commitPassiveMountEffects_begin (react-dom_client.js?v=2d8c18c0:18115:15)
at commitPassiveMountEffects (react-dom_client.js?v=2d8c18c0:18105:11)
at flushPassiveEffectsImpl (react-dom_client.js?v=2d8c18c0:19486:11).

code - import styles from "./style.module.css";
import YouTube from "react-youtube";
import { useContext, useEffect, useRef, useState } from "react";
import { FaPlay, FaCompressArrowsAlt, FaExpandArrowsAlt } from "react-icons/fa";
import {
TbPlayerSkipForwardFilled,
TbPlayerSkipBackFilled,
TbPlayerPauseFilled,
} from "react-icons/tb";
import { ImVolumeMute2, ImVolumeHigh } from "react-icons/im";
import { BsCameraVideoFill, BsFillCameraVideoOffFill } from "react-icons/bs";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { BsPlusCircle } from "react-icons/bs";
import { IoIosArrowUp, IoIosArrowDown } from "react-icons/io";
import HandlePlayingSongContext from "../../../contexts/HandlePlayingSong";
import AddToPlaylist from "../AddToPlaylist";
import HandleFavoriteSong from "../../HandleFavoriteSong";
import ShowPopups from "../../../contexts/ShowPopups";
import Playlists from "../../../contexts/Playlists";
import Token from "../../../contexts/Token";

function Footer({ backgroundVideo, setBackgroundVideo, screenWidth }) {
const [isPlayerReady, setIsPlayerReady] = useState(false);

const { token } = useContext(Token);
const { playedPlaylist } = useContext(Playlists);
const {
isSongPlaying,
setIsSongPlaying,
songs,
songPlayed,
skipBackOrForward,
} = useContext(HandlePlayingSongContext);
const { showAddToPlaylistPopup, setShowAddToPlaylistPopup } =
useContext(ShowPopups);
const [volume, setVolume] = useState(50);
const [showFooter, setShowFooter] = useState(true);
const [minutes, setMinutes] = useState(0);
const [seconds, setSeconds] = useState(0);
const [songProgress, setSongProgress] = useState(0);
const navigate = useNavigate();
const location = useLocation();
const playerRef = useRef(null);
const opts = {
playerVars: {
autoplay: 1,
// autoplay: 0,
fs: 1,
controls: 0,
disablekb: 1,
modestbranding: 1,
showinfo: 0,
rel: 0,
Loop: 1,
},
};

useEffect(() => {
const params = new URLSearchParams(location.search);
const playSong = params.get("playSong") === "true";

if (playSong) {
  if (playSong && isPlayerReady) {
    setIsSongPlaying(true);

    setBackgroundVideo(true);
  }
}

}, [location.search, isPlayerReady]);

const handlePlayerStateChange = (e) => {
let interval;
const handleSongEnd = () => {
clearInterval(interval);
setMinutes(0);
setSeconds(0);
};
if (e.data === window.YT.PlayerState.PLAYING) {
interval = setInterval(() => {
const duration = playerRef?.current?.getDuration();
const currentTime = playerRef?.current?.getCurrentTime();
const progress = (currentTime / duration) * 100;
setSongProgress(progress);
const player = e.target;
const newCurrentTime = player?.getCurrentTime();
const newMinutes = Math.floor(newCurrentTime / 60);
const newSeconds = Math.floor(newCurrentTime % 60);
setMinutes(newMinutes);
setSeconds(newSeconds);
if (currentTime >= duration) {
handleSongEnd();
}
}, 1000);
} else {
clearInterval(interval);
setMinutes(0);
setSeconds(0);
}
};

const handleProgressChange = (e) => {
const progress = parseInt(e.target.value);
const duration = playerRef.current.getDuration();
const seekTime = (progress / 100) * duration;
playerRef.current.seekTo(seekTime);
setSongProgress(progress);
};

const handlePause = () => {
setIsSongPlaying(false);
};

const handlePlay = () => {
setIsSongPlaying(true);
};

useEffect(() => {
console.log(222);
if (isPlayerReady) {
if (playerRef?.current) {
playerRef.current?.setVolume(volume);
if (isSongPlaying) {
playerRef.current?.playVideo();
} else {
playerRef.current?.pauseVideo();
}
}
}
}, [isSongPlaying, volume, songPlayed]);

const handleBackgroundVideo = () => {
setBackgroundVideo((prev) => !prev);
};

const handleVolumeChange = (e) => {
const newVolume = Number(e.target.value);
if (isPlayerReady) setVolume(newVolume);
};

const handleMute = () => {
setVolume(0);
};

const handleUnmute = () => {
setVolume(50);
};

return (
<>


<YouTube
onStateChange={handlePlayerStateChange}
style={{
display:
location.pathname === "/Video" || backgroundVideo
? "block"
: "none",
}}
// videoId={playedPlaylist ? songPlayed?.videoId : songPlayed?.id} old api
videoId={songPlayed?.videoId}
opts={opts}
autoplay
onReady={(e) => {
playerRef.current = e.target;
setMinutes(0);
setSeconds(0);
setIsPlayerReady(true);
}}
onEnd={() => {
playedPlaylist
? skipBackOrForward("forward", playedPlaylist)
: skipBackOrForward("forward", songs);
setMinutes(0);
setSeconds(0);
}}
/>

<div
className={${styles.arrowButton} ${ !showFooter && styles.arrowUpButton }}
onClick={() => setShowFooter((prev) => !prev)}
>
{showFooter ? : }

{showFooter && (


{location.pathname === "/Video" ? (
<Link
onClick={() => {
navigate(-1);
}}
>
{
<BsFillCameraVideoOffFill
size={25}
style={{ marginLeft: "0.2vw" }}
/>
}

) : (
<Link to={"Video"}>
{
<BsCameraVideoFill
size={25}
style={{
display: screenWidth < 513 && "none",
marginLeft: "0.2vw",
}}
/>
}

)}
{!backgroundVideo && (

<FaExpandArrowsAlt
size={24}
onClick={handleBackgroundVideo}
className={styles.iconButton}
style={{ display: screenWidth < 513 && "none" }}
/>

)}
{backgroundVideo && (
<FaCompressArrowsAlt
size={24}
onClick={handleBackgroundVideo}
className={styles.iconButton}
style={{ display: screenWidth < 513 && "none" }}
/>
)}


<img
className={isSongPlaying ? styles.spinImg : ""}
src={
// playedPlaylist old api
// ? songPlayed?.channelImg
// : songPlayed?.channel?.icon

            playedPlaylist
              ? songPlayed?.channelImg
              : songPlayed?.thumbnail[0]?.url
          }
          onError={(e) => {
            // e.target.src = playedPlaylist   old api
            //   ? songPlayed?.songImg
            //   : songPlayed?.thumbnail?.url;
            e.target.src = playedPlaylist
              ? songPlayed?.songImg
              : songPlayed?.thumbnail[1]?.url;
          }}
        />
        <span>
          {
            // playedPlaylist   old api
            //   ? songPlayed?.channelName
            //   : songPlayed?.channel?.name
            playedPlaylist
              ? songPlayed?.channelName
              : songPlayed?.channelTitle
          }
        </span>
      </div>
      <div className={styles.centerItemsContainer}>
        <div className={styles.palyingButtonsContainer}>
          <HandleFavoriteSong screenWidth={screenWidth} />
          <TbPlayerSkipBackFilled
            onClick={() => {
              playedPlaylist
                ? skipBackOrForward("back", playedPlaylist)
                : skipBackOrForward("back", songs);
            }}
            size={screenWidth > 500 ? 19 : 35}
            className={styles.iconButton}
          />
          {!isSongPlaying && (
            <FaPlay
              className={`${styles.iconButton} ${styles.playAndPauseButton}`}
              onClick={handlePlay}
              size={screenWidth > 500 ? 30 : 40}
              // size={40}
            />
          )}
          {isSongPlaying && (
            <TbPlayerPauseFilled
              className={`${styles.iconButton} ${styles.playAndPauseButton}`}
              onClick={handlePause}
              size={screenWidth > 500 ? 30 : 40}
            />
          )}
          <TbPlayerSkipForwardFilled
            onClick={() => {
              playedPlaylist
                ? skipBackOrForward("forward", playedPlaylist)
                : skipBackOrForward("forward", songs);
            }}
            size={screenWidth > 500 ? 19 : 35}
            className={styles.iconButton}
          />
          <div className={styles.AddToPlaylist}>
            <BsPlusCircle
              onClick={() => {
                if (token) setShowAddToPlaylistPopup((prev) => !prev);
              }}
              size={19}
              className={`${styles.iconButton} ${styles.addToPlaylistButton}`}
            />
          </div>
        </div>
        <div className={styles.progressContainer}>
          <div className={styles.progressTime}>
            {`${minutes.toString().padStart(2, "0")}:${seconds
              .toString()
              .padStart(2, "0")}`}
          </div>
          <input
            type="range"
            min="0"
            max="100"
            value={songProgress}
            onChange={handleProgressChange}
            className={styles.progressInput}
          />
          <span className={styles.progressTime}>
            {songPlayed?.duration_formatted}
          </span>
        </div>
      </div>
      <div className={styles.volume}>
        {volume == 0 ? (
          <ImVolumeMute2
            size={22}
            color="red"
            onClick={handleUnmute}
            className={styles.iconButton}
          />
        ) : (
          <ImVolumeHigh
            size={22}
            onClick={handleMute}
            className={styles.volumeHighButton}
          />
        )}
        <input
          type="range"
          min="0"
          max="100"
          value={volume}
          onChange={handleVolumeChange}
          className={styles.volumeInput}
        />
      </div>
    </div>
  )}
  {showAddToPlaylistPopup && showFooter && <AddToPlaylist />}
</>

);
}
export default Footer;

@degs098
Copy link

degs098 commented Oct 26, 2024

+1 to this issue. Having the same behavior when playerRef.current?.playVideo?.() is called from my code.

@johnstonphilip
Copy link

I am seeing this also. I wonder if something changed on youtube's side, as that's where the error is actually emitting from:

Uncaught TypeError: Cannot read properties of null (reading 'src')
    at q.sendMessage (www-widgetapi.js:200:95)

@johnstonphilip
Copy link

Actually, I just confirmed it's more related this issue:
https://stackoverflow.com/questions/45513049/vue-youtube-embed-cannot-read-property-src-of-null

If you unmount the player, then re-mount it, this can happen. The best approach seems to be to keep the player mounted at all times, even if unused. I have had to re-arrange the structure of my react app to do this.

@johnstonphilip
Copy link

One other thing I noticed, it seems like this can be caused by timing issues when calling a youtube api function like playVideo, but where the player is not fully mounted to the screen.

I have found I can prevent this by waiting until the following is true:

if ( ! player || ! player?.h || ! player?.g ) {
         // Don't do anything if the player isn't ready
		return;
}

// Now I can call player.playVideo();

@YonatanAriel
Copy link
Author

YonatanAriel commented Nov 9, 2024

Thank you! I tried to check if the player was ready before, but it didn't help. I used what you wrote (and added some of my own) and it seems to work!
code:

const isPlayerFullyReady = () => {
   try {
     return (
       playerRef.current &&
       playerRef.current?.h &&
       playerRef.current?.g &&
       typeof playerRef.current.getCurrentTime === "function" &&
       typeof playerRef.current.getPlayerState === "function"
     );
   } catch (e) {
     return false;
   }
 };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants