【问题标题】:React: DOMException: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to set local answer sdp: Called in wrong state: stable [duplicate]反应:DOMException:无法在 \'RTCPeerConnection\' 上执行 \'setLocalDescription\':无法设置本地答案 sdp:在错误状态下调用:stable
【发布时间】:2022-10-01 13:46:08
【问题描述】:

我正在使用peerjs 中的React 和vite 制作一个直播应用程序。

我知道已经有很多这样的问题,但这是针对ReactJs 的。我找不到任何与 React 相关的解决方案。

这是我的webRTC.ts 文件。

import Peer, { DataConnection, MediaConnection } from \"peerjs\";
import socket from \"./socket\";
import { JOIN_LECTURE } from \"./socketaction\";

export const getUserStream: () => Promise<MediaStream> =
  (): Promise<MediaStream> => {
    return navigator.mediaDevices.getUserMedia({
      video: true,
      audio: true,
    });
  };
export const getScreenStream: () => Promise<MediaStream> =
  (): Promise<MediaStream> => {
    return navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
  };

export const initPeer: (peer: Peer, name: string) => void = (
  peer: Peer,
  name: string
) => {
  // On Error
  peer.on(\"error\", (err: Error): void => {
    console.error(err);
  });
  // On Open
  peer.on(\"open\", (id: string): void => {
    console.log(\"[p]open\", name);
    console.log(id);
  });
  // On Connection
  peer.addListener(\"connection\", (conn: DataConnection): void => {
    conn.on(\"data\", (data: unknown): void => {
      console.log(data, \"data\");
    });
  });
};

export const answerCall = (
  peer: Peer,
  lectureId: string,
  cb: (stream: MediaStream, type: \"user\" | \"screen\") => void
): void => {
  peer.addListener(\"open\", () => {
    socket.emit(JOIN_LECTURE, {
      peerId: peer.id,
      lectureId: lectureId,
    });
    // socket.emit(GET_STREAM);
  });
  peer.on(\"call\", (call: MediaConnection): void => {
    call.answer();
    call.on(\"stream\", (stream: MediaStream): void => {
      cb(stream, call.metadata.streamType);
    });
    call.on(\"error\", console.error);
    call.on(\"close\", (): void => {
      console.log(\"call closed\");
    });
  });
};

export const shareUser = (
  peer: Peer,
  stream: MediaStream,
  studentId: string
) => {
  if (peer && studentId) {
    console.log(studentId);
    const conn = peer.connect(studentId);
    conn.on(\"open\", () => {
      const call: MediaConnection = peer.call(studentId, stream, {
        metadata: {
          streamType: \"user\",
        },
      });
      call.on(\"error\", console.error);
      call.on(\"close\", (): void => {
        console.log(\"call closed\");
      });
    });
    conn.on(\"data\", console.log);
    conn.on(\"error\", console.error);
    conn.on(\"iceStateChanged\", () => console.log(\"IceStageChanged\"));
  }
};

这是我的main.tsx 文件

import ReactDOM from \"react-dom/client\";
import App from \"./App\";
import \"./index.css\";
import { ThemeProvider } from \"@mui/system\";
import MuiTheme from \"./mui-theme\";
import { Provider } from \"react-redux\";
import { store } from \"./redux/store\";
import { ToastContainer } from \"react-toastify\";
import \"react-toastify/dist/ReactToastify.css\";
import { CookiesProvider } from \"react-cookie\";
import { StrictMode } from \"react\";

ReactDOM.createRoot(document.getElementById(\"root\") as HTMLElement).render(
  <StrictMode>
    <Provider store={store}>
      <CookiesProvider>
        <ThemeProvider theme={MuiTheme}>
          <App />
        </ThemeProvider>
        <ToastContainer theme=\"dark\" position=\"bottom-right\" />
      </CookiesProvider>
    </Provider>
  </StrictMode>
);

这是我的liveLecture.tsx 文件。

import {
  CallEndRounded,
  ChatRounded,
  DoNotTouchRounded,
  FullscreenRounded,
  MicOffRounded,
  MicRounded,
  PanToolRounded,
  PausePresentationRounded,
  PresentToAllRounded,
  SendRounded,
  SpeakerNotesOffRounded,
  VideocamOffRounded,
  VideocamRounded,
  ViewComfyOutlined,
  ViewComfyRounded,
} from \"@mui/icons-material\";
import { Avatar, IconButton, Input, InputAdornment } from \"@mui/material\";
import { AnyAction, Dispatch } from \"@reduxjs/toolkit\";
import Peer, { DataConnection, MediaConnection } from \"peerjs\";
import { useEffect, useState } from \"react\";
import { useDispatch } from \"react-redux\";
import { useSelector } from \"react-redux\";
import { useParams } from \"react-router-dom\";
import Logo from \"../components/logo\";
import VideoPlayer from \"../components/videoplayer\";
import { User } from \"../redux/slices/user\";
import { RootState } from \"../redux/store\";
import socket from \"../utils/socket\";
import { GET_STREAM, IS_ADMIN, JOIN_LECTURE } from \"../utils/socketaction\";
import {
  answerCall,
  getScreenStream,
  getUserStream,
  initPeer,
  shareUser,
} from \"../utils/webRTC\";

interface Paused {
  video: boolean;
  audio: boolean;
}

function LiveLecture() {
  const { lectureId } = useParams();

  const user: User = useSelector((state: RootState): User => state.user);

  const [userStream, setUserStream] = useState<MediaStream | undefined>();
  const [screenStream, setScreenStream] = useState<MediaStream | undefined>();
  const [isHandRaised, setIsHandRaised] = useState<boolean>(false);

  const [isChatOpen, setIsChatOpen] = useState<boolean>(true);
  const [isPresentationView, setIsPresentationView] = useState<boolean>(true);

  const [paused, setPaused] = useState<Paused>({
    audio: true,
    video: true,
  });

  const [isAdmin, setIsAdmin] = useState<boolean>(false);

  const [userPeer, setUserPeer] = useState<Peer>(new Peer());
  const [screenPeer, setScreenPeer] = useState<Peer>(new Peer());

  const [isFullScreen, setIsFullScreen] = useState(false);

  const dispatch: Dispatch<AnyAction> = useDispatch();

  const shareUserStream = (studentId: string) => {
    if (userStream) {
      shareUser(userPeer, userStream, studentId);
    } else {
      getUserStream().then((stream: MediaStream): void => {
        setUserStream(stream);
        console.log(userPeer, studentId);
        shareUser(userPeer, stream, studentId);
      });
    }
  };

  useEffect((): (() => void) => {
    if (userPeer && lectureId) {
      initPeer(userPeer, \"user\");
      answerCall(
        userPeer,
        lectureId,
        (stream: MediaStream, type: \"user\" | \"screen\"): void => {
          console.log(\"second\");
          if (type == \"user\") {
            setUserStream(stream);
          } else {
            setScreenStream(stream);
          }
        }
      );
    }
    return (): void => {};
  }, [userPeer]);

  useEffect((): (() => void) => {
    if (screenPeer) {
      initPeer(screenPeer, \"screen\");
    }
    return (): void => {};
  }, [screenPeer]);

  useEffect((): (() => void) => {
    socket.on(IS_ADMIN, (admin: boolean) => {
      setIsAdmin(admin);
      if (admin) {
        socket.on(GET_STREAM, (studentPeerId) => {
          // call the user
          studentPeerId && shareUserStream(studentPeerId);
        });
      }
      console.log(admin);
    });
    return (): void => {};
  }, []);

  return (
    <div className=\"flex overflow-hidden flex-col w-full h-screen\">
      <div className=\"w-full flex px-10 bg-gray-900 shadow-lg py-3\">
        <div className=\" flex sm:gap-6 gap-4 divide-x-2 justify-center items-center text-2xl font-semibold text-white\">
          <div className=\"hidden sm:block\">
            <Logo />
          </div>
          <div className=\"md:pl-6 pl-4\">Batch Name</div>
          <div className=\"select-none ring-2 ring-red-500 bg-white text-red-500 font-bold uppercase px-2 rounded-lg\">
            Live
          </div>
        </div>
      </div>
      <div className=\"flex py-4 lg:justify-around lg:flex-row bg-secondary-100 grow h-[calc(100vh-16rem)] flex-col\">
        <div className=\"flex grow gap-3 flex-col justify-between items-center\">
          <div
            className={`flex justify-center items-center grow px-4 lg:w-full`}
          >
            <div
              className={`grid ${
                !isChatOpen && \"px-10\"
              } w-full gap-4 grid-cols-4 grid-row-4`}
            >
              <div
                className={`${
                  isChatOpen ? \"col-span-1\" : \"col-span-3 sm:col-span-1\"
                }`}
              >
                {/* secondary player */}
                <VideoPlayer
                  stream={isPresentationView ? userStream : screenStream}
                />
              </div>
              <div
                className={`col-span-3 px-2 grow flex justify-center items-center`}
              >
                {/* primary player */}
                <VideoPlayer
                  isFullScreen={isFullScreen}
                  stream={isPresentationView ? screenStream : userStream}
                />
              </div>
            </div>
          </div>
          <div className=\"justify-center items-center\">
            <div className=\"py-2 px-6 rounded-full bg-gray-900 text-gray-500 flex gap-2 sm:gap-6 justify-center items-center\">
              {isAdmin ? (
                <>
                  <IconButton
                    onClick={(): void => {
                      setPaused(
                        (pp: Paused): Paused => ({
                          ...pp,
                          audio: !pp.audio,
                        })
                      );
                    }}
                    color=\"inherit\"
                  >
                    {paused.audio ? (
                      <MicOffRounded color=\"inherit\" />
                    ) : (
                      <MicRounded color=\"inherit\" />
                    )}
                  </IconButton>
                  <IconButton
                    onClick={(): void => {
                      setPaused(
                        (pp: Paused): Paused => ({
                          ...pp,
                          video: !pp.video,
                        })
                      );
                    }}
                    color=\"inherit\"
                  >
                    {paused.video ? (
                      <VideocamOffRounded color=\"inherit\" />
                    ) : (
                      <VideocamRounded color=\"inherit\" />
                    )}
                  </IconButton>
                  <IconButton
                    onClick={() => {
                      if (!screenStream) {
                        const f: string | null = prompt(\"fId:\");
                        if (screenPeer && f) {
                          getScreenStream().then((stream: MediaStream) => {
                            setScreenStream(stream);
                            const conn: DataConnection = screenPeer.connect(f);
                            conn.on(\"open\", (): void => {
                              const call: MediaConnection = screenPeer.call(
                                f,
                                stream,
                                {
                                  metadata: {
                                    streamType: \"screen\",
                                  },
                                }
                              );
                              call.on(\"error\", console.error);
                              call.on(\"close\", (): void => {
                                console.log(\"call closed\");
                              });
                            });
                          });
                        }
                      } else {
                        screenStream
                          .getTracks()
                          .forEach((track: MediaStreamTrack): void =>
                            track.stop()
                          );
                        setScreenStream(undefined);
                      }
                    }}
                    color=\"inherit\"
                  >
                    {screenStream ? (
                      <PausePresentationRounded color=\"inherit\" />
                    ) : (
                      <PresentToAllRounded color=\"inherit\" />
                    )}
                  </IconButton>
                  <IconButton
                    onClick={() => {
                      setIsPresentationView((pipv) => !pipv);
                    }}
                    color=\"inherit\"
                  >
                    {isPresentationView ? (
                      <ViewComfyOutlined color=\"inherit\" />
                    ) : (
                      <ViewComfyRounded color=\"inherit\" />
                    )}
                  </IconButton>
                </>
              ) : (
                <IconButton color=\"inherit\">
                  {isHandRaised ? (
                    <PanToolRounded color=\"inherit\" />
                  ) : (
                    <DoNotTouchRounded color=\"inherit\" />
                  )}
                </IconButton>
              )}
              <IconButton
                color=\"inherit\"
                onClick={(): void => {
                  setIsChatOpen((pico: boolean): boolean => !pico);
                }}
              >
                {isChatOpen ? (
                  <SpeakerNotesOffRounded color=\"inherit\" />
                ) : (
                  <ChatRounded color=\"inherit\" />
                )}
              </IconButton>
              <IconButton
                onClick={() => {
                  setIsFullScreen((pifs: boolean): boolean => !pifs);
                  setIsAdmin(true);
                }}
                color=\"inherit\"
              >
                <FullscreenRounded color=\"inherit\" />
              </IconButton>
              <IconButton
                onClick={(): void => {
                  // const f: string | null = window.prompt(\"fId\");
                  // getUserStream().then((stream: MediaStream): void => {
                  //   setUserStream(stream);
                  //   if (userPeer && f) {
                  //     const conn: DataConnection = userPeer.connect(f);
                  //     conn.on(\"open\", (): void => {
                  //       const call: MediaConnection = userPeer.call(f, stream, {
                  //         metadata: {
                  //           streamType: \"user\",
                  //         },
                  //       });
                  //       call.on(\"error\", console.error);
                  //       call.on(\"close\", (): void => {
                  //         console.log(\"call closed\");
                  //       });
                  //     });
                  //   }
                  // });
                  const f: string | null = window.prompt(\"fId\");
                  f && shareUserStream(f);
                }}
                sx={{
                  bgcolor: \"#550000\",
                }}
                color=\"error\"
              >
                <div className=\"flex justify-center items-center grow\">
                  <CallEndRounded />
                </div>
              </IconButton>
            </div>
          </div>
        </div>
        <div className=\"px-3 py-4\">
          <div
            className={`sm:h-1/4 ${
              !isChatOpen && \"hidden\"
            } h-2/5 sm:h-1/4 lg:h-[calc(100%-5rem)] w-full lg:min-w-[20rem]`}
          >
            <div className=\"py-2 px-4 bg-primary-400 text-white shadow-lg rounded-t-lg uppercase font-semibold select-none\">
              live chats
            </div>
            <div className=\"flex px-4 h-full overflow-auto flex-col bg-primary-400\">
              <div className=\"flex flex-col gap-2 py-2 py-3overflow-auto\">
                <div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
                  <div>
                    <Avatar />
                  </div>
                  <div className=\"flex flex-col gap-1\">
                    <div className=\"font-semibold text-sm\">Name Name</div>
                    <div className=\"text-sm\">
                      This is some chat message to test the ui responsiveness
                    </div>
                  </div>
                </div>
                <div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
                  <div>
                    <Avatar />
                  </div>
                  <div className=\"flex flex-col gap-1\">
                    <div className=\"font-semibold text-sm\">Name Name</div>
                    <div className=\"text-sm\">
                      This is some chat message to test the ui responsiveness
                    </div>
                  </div>
                </div>
                <div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
                  <div>
                    <Avatar />
                  </div>
                  <div className=\"flex flex-col gap-1\">
                    <div className=\"font-semibold text-sm\">Name Name</div>
                    <div className=\"text-sm\">
                      This is some chat message to test the ui responsiveness
                    </div>
                  </div>
                </div>
                <div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
                  <div>
                    <Avatar />
                  </div>
                  <div className=\"flex flex-col gap-1\">
                    <div className=\"font-semibold text-sm\">Name Name</div>
                    <div className=\"text-sm\">
                      This is some chat message to test the ui responsiveness
                    </div>
                  </div>
                </div>
                <div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
                  <div>
                    <Avatar />
                  </div>
                  <div className=\"flex flex-col gap-1\">
                    <div className=\"font-semibold text-sm\">Name Name</div>
                    <div className=\"text-sm\">
                      This is some chat message to test the ui responsiveness
                    </div>
                  </div>
                </div>
                <div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
                  <div>
                    <Avatar />
                  </div>
                  <div className=\"flex flex-col gap-1\">
                    <div className=\"font-semibold text-sm\">Name Name</div>
                    <div className=\"text-sm\">This is some chat message</div>
                  </div>
                </div>
                <div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
                  <div>
                    <Avatar />
                  </div>
                  <div className=\"flex flex-col gap-1\">
                    <div className=\"font-semibold text-sm\">Name Name</div>
                    <div className=\"text-sm\">
                      This is some chat message to test the ui responsiveness
                    </div>
                  </div>
                </div>
                <div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
                  <div>
                    <Avatar />
                  </div>
                  <div className=\"flex flex-col gap-1\">
                    <div className=\"font-semibold text-sm\">Name Name</div>
                    <div className=\"text-sm\">
                      This is some chat message to test the ui responsiveness
                    </div>
                  </div>
                </div>
                <div className=\"flex bg-white rounded-md gap-2 px-4 py-2 items-center\">
                  <div>
                    <Avatar />
                  </div>
                  <div className=\"flex flex-col gap-1\">
                    <div className=\"font-semibold text-sm\">Name Name</div>
                    <div className=\"text-sm\">
                      This is some chat message to test the ui responsiveness
                    </div>
                  </div>
                </div>
              </div>
            </div>
            <div className=\"py-2 px-4 bg-primary-400 rounded-b-md\">
              <Input
                // value={message}
                // onChange={(e) => {
                //   setMessage(e.target.value);
                // }}
                placeholder=\"message\"
                inputProps={{
                  className: \"no-scrollbar\",
                }}
                fullWidth={true}
                className=\"bg-primary-400\"
                multiline
                maxRows={3}
                endAdornment={
                  <InputAdornment position=\"end\">
                    <IconButton>
                      <SendRounded className=\"cursor-pointer\" />
                    </IconButton>
                  </InputAdornment>
                }
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

export default LiveLecture;

最后这是我用于 socket.io 连接的服务器代码

socket.on(
    JOIN_LECTURE,
    ({ lectureId, peerId }: { lectureId: string; peerId: string }): void => {
      // verify user identity

      socket.join(lectureId);
      socket.to(lectureId).emit(GET_STREAM, peerId);
      console.log(colors.blueBright(`[${socket.id}] joined ${lectureId}`));

      // check if the connected user is admin;
      // temporarily setting the first user as admin; pink;
      const clientsInRoom: Set<string> | undefined =
        io.sockets.adapter.rooms.get(lectureId);
      const viewerCount: number = (clientsInRoom ? clientsInRoom.size : 0) - 1;
      const isAdmin: boolean = !viewerCount;
      socket.emit(IS_ADMIN, isAdmin);
      // console.log(io.sockets.adapter.rooms);
      socket.addListener(disconnect, () => {
        console.log(\"disconnect\");
        // temporarily transferring admin rights to the next socket; pink;
        if (isAdmin) {
          const nextAdmin = clientsInRoom?.entries().next();
          if (nextAdmin) {
            console.log(nextAdmin, \"nextAdmin\");
            io.sockets.to(nextAdmin.value).emit(IS_ADMIN, true);
          }
        }
      });
    }
  );```

    标签: javascript reactjs socket.io webrtc peerjs


    【解决方案1】:

    经过一些研究并回答了我自己的问题后,我修复了错误。

    通常,此错误来自多次接听同一个电话(或者我认为来自this question's 答案)。您可以在ReactJs 中看到如果您没有遇到此问题。

    我发现 React 调用 useEffect 两次是为了检测代码中的任何问题并警告您。(在this question 中查找更多信息)

    因此,我只是从 main.tsx 中删除了 StrictMode wrapper,例如:

    ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
        <Provider store={store}>
          <CookiesProvider>
            <ThemeProvider theme={MuiTheme}>
              <App />
            </ThemeProvider>
            <ToastContainer theme="dark" position="bottom-right" />
          </CookiesProvider>
        </Provider>
    );
    

    现在我的应用程序可以正常工作了。

    注意:请参阅在 react 中删除 StrictMode 的缺点。

    【讨论】:

      猜你喜欢
      • 2022-08-21
      • 2021-05-09
      • 2018-08-04
      • 2016-09-21
      • 2019-08-18
      • 1970-01-01
      • 2016-10-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多