使用 React 和 daily-js 在几分钟内构建视频聊天应用程序

2025-06-07

使用 React 和 daily-js 在几分钟内构建视频聊天应用程序

随着视频聊天应用的兴起(原因显而易见),能够快速为应用和网站添加视频通话功能变得越来越重要。视频通话的可定制性越高,就越有利于打造独特的用户体验。

这篇文章将引导您了解如何使用 React 和Daily API构建自定义视频聊天应用程序

React 演示应用中的两个人在视频通话中微笑

我们将构建什么

在我们的应用中,当用户点击开始通话时,应用会创建一个会议室,将会议室的 URL 传递给一个新的每日通话对象,然后加入通话。通话对象会跟踪会议的重要信息,例如其他参与者(包括他们的音频和视频轨道)以及他们在通话中所做的事情(例如,静音麦克风或离开),并提供与会议交互的方法。应用会利用此对象相应地更新其状态,并执行诸如静音或屏幕共享之类的用户操作。当用户离开会议室时,通话对象将被销毁。

构建它需要什么

  • 每日帐户如果您还没有帐户,请注册一个。
  • 克隆的daily-demos/call-object-reactGithub 存储库:遵循本教程并启动和运行演示应用程序的最快方法是克隆此存储库
  • 熟悉 React:在这篇文章中,我们跳过了很多与 Daily 无关的代码,因此值得复习一下 React 和/或 hooks [0]。

一旦您拥有了这些东西,我们就可以开始了!

构建并运行应用程序

克隆存储库



cd call-object-react
npm i
npm run dev


Enter fullscreen mode Exit fullscreen mode

现在,打开浏览器,localhost:<port>使用运行上述命令后终端打印的端口号前往 。您应该会在本地运行一个与现场演示对应的应用程序:您可以点击 开始通话,并在新标签页中与其他参与者或自己共享链接。

《星球大战:魅影危机》中的阿纳金·天行者惊呼它正在发挥作用

它能发挥作用真是太好了...但它是如何发挥作用的呢?

应用程序的工作原理

日常概念

在深入研究代码之前,让我们先了解一些日常基础知识。

调用对象

Daily 通话对象就像是一条通往 Daily API 的直达线路。它让我们能够对视频通话进行最精细的控制,访问其最底层的基础功能,例如参与者的视频和音轨。调用DailyIframe.createCallObject()会创建一个通话对象。创建后,我们将会议室 URL 传递给该通话对象即可加入通话。

除此之外,呼叫对象还会跟踪我们的呼叫状态,包括会议状态和参与者状态。

状态 #1:会议状态

会议状态跟踪当前(通常称为“本地”)参与者在通话过程中的所处位置。参与者可能正在开始加入通话、正在通话中、已离开通话或遇到错误。

我们可以通过 call 对象检查通话的会议状态callObject.meetingState()。例如,如果参与者正在加入会议,则会返回“joining-meeting”事件。

会议状态变化会触发诸如“加入会议”之类的事件。我们可以使用 为这些状态变化添加事件监听器callObject.on()

状态 #2:参与者状态

参与者状态监控通话中的所有参与者(包括当前用户)以及他们与其他人共享的视频、音频或其他媒体。

callObject.participants()返回一组参与者对象,以 ID(对于当前用户,则为“本地”)为键。每个参与者对象包含类似 的字段tracks,其中包含参与者的原始音频和视频轨道及其可播放状态。

事件“participant-joined”、“participant-left”和“participant-updated”用于广播参与者状态的变化。前两个事件仅在当前本地参与者以外的参与者加入或离开时发送而后者则在任何参与者(包括本地参与者)切换摄像头和麦克风时触发。

现在我们已经介绍了每日呼叫对象及其状态,我们就可以查看我们的应用程序了。

代码中发生了什么

App.js:创建、加入和离开通话

在深入了解会议各个阶段的细节之前,我们先来看看如何连接顶级事件监听器。在 App.js 中,我们监听 的变化callObject.meetingState(),以便根据本地参与者在用户旅程中所处的位置(通话中、通话结束或遇到错误)更新他们的 UI:

// In App.js
useEffect(() => {
if (!callObject) return;
const events = ["joined-meeting", "left-meeting", "error"];
function handleNewMeetingState(event) {
event && logDailyEvent(event);
switch (callObject.meetingState()) {
case "joined-meeting":
// update component state to a "joined" state...
break;
case "left-meeting":
callObject.destroy().then(() => {
// update component state to a "left" state...
});
break;
case "error":
// update component state to an "error" state...
break;
default:
break;
}
}
// Use initial state
handleNewMeetingState();
// Listen for changes in state
for (const event of events) {
callObject.on(event, handleNewMeetingState);
}
// Stop listening for changes in state
return function cleanup() {
for (const event of events) {
callObject.off(event, handleNewMeetingState);
}
};
}, [callObject]);
view raw App.js hosted with ❤ by GitHub

当本地参与者离开会议时,我们会调用callObject.destroy()。这样做是为了清理调用对象的全局占用空间,为我们的应用将来使用不同的创建时选项创建另一个调用对象打开大门。

创建通话

当参与者点击开始通话时,他们会调用该createCall()功能来创建一个短暂的、仅用于演示的房间。

在实际的生产代码中,您需要通过从后端服务器调用Daily REST API来创建房间,以避免将 API 密钥存储在客户端 JavaScript [1] 中。

加入通话

一旦我们有了房间,我们将通过调用调用对象 [2] 上的.join()方法来加入它。

留下通话

当参与者点击“离开”按钮时,我们将通过调用调用对象上的leave()方法来启动该过程 [3, 4]。

Call.js 和 callState.js:使用状态来确定呼叫显示

现在我们知道了通话中各种操作是如何进行的,下一步就是了解这些操作如何影响显示。这需要监控参与者的状态,以便显示通话中的参与者及其音视频轨。

虽然 App.js 监听了callObject.meetingState(),但在 Call.js 中我们将监听callObject.participantState()并相应地更新我们的组件状态 [5]。

我们的演示应用程序将每个参与者(包括当前用户)显示为他们自己的“图块”,并且还将任何屏幕共享显示为独立于进行共享的参与者的自己的图块。

为了实现这一点,我们映射callObject.participantState()到调用的组件状态,具体来说是映射到 callState.js 中的一组“调用项”中:

每个通话项目对应一个通话参与者,存储参与者的视频轨道、音频轨道和一个布尔值,用于指示参与者是否正在加入通话 [6]。

为了填充调用项,我们调用我们的getCallItems()函数,该函数循环遍历参与者状态:

我们在 Call.js 中导入 callState,在其中调用getTiles()函数将参与者的视频和音频轨道传递到各自的图块组件。

现在让我们仔细看看这些瓷砖。

粉色和黄色瓷砖变换颜色

Tile.js:显示每个参与者的视频流

我们的每个 Tile 组件都包含一个<video>and/or<audio>元素。每个标签都引用其对应的 DOM 元素 [7]。请注意autoPlay muted playsInline属性。这些属性集可让您的音频和视频在 Chrome、Safari 和 Firefox 上自动播放。

接下来:让参与者控制是否显示他们的视频以及共享他们的音频或屏幕。

Tray.js:启用参与者控件

我们将再次使用参与者状态来确定我们是否正在主动共享音频、视频和屏幕。

我们将特别关注callObject.participants().local,因为我们关心的是针对当前用户(或本地用户)调整用户界面。我们唯一需要监听的事件是“participant-updated”[8]。

通过我们的事件监听器处理状态更新,我们可以连接我们的按钮来处理相关callObject方法来控制用户输入:.setLocalVideo.setLocalAudio、 和.startScreenShare.stopScreenShare

下一步要添加什么

恭喜!如果您已经读到这里,那么您已经对您的自定义视频聊天应用有了大致的了解。想要更深入地了解代码,请查看Daily 博客上的演示代码,了解该演示如何处理特殊情况。或者,您也可以深入研究我们的演示代码库

按钮

要查看 Daily API 提供的所有其他内容,请喝杯茶并前往docs.daily.co享受一些有趣的晚间读物。

感谢您的阅读!一如既往,我们非常想知道您的想法,以及我们如何才能更好地帮助您打造下一个优秀的视频聊天应用。所以,请随时联系我们

杯子里的恐龙,里面有一个茶包

脚注

[0] 如果您想熟悉 React 并稍后再回来,请查看 DEV 上的许多优秀资源(例如Ali Spittel 的简介),或查看 React 文档以获取有关hooks的更多信息。
[1] 我们的队友写了一篇关于如何在 Glitch 上设置即时 Daily 服务器的精彩文章。
[2] 请注意,由于我们destroy()在每个通话结束后都会调用我们的 call 对象,因此我们需要创建一个新的 call 对象才能加入房间。这不是绝对必要的 - 如果您愿意,您可以在应用程序的整个生命周期内保留一个 call 对象,但是,正如我们之前提到的,我们更喜欢这种方法,为将来不同配置的 call 对象敞开大门。
[3] 您可能已经注意到join()leave()call 对象操作都是异步的destroy()。为避免未定义的行为和应用程序错误(例如同时离开和销毁 call 对象),重要的是防止在另一个 call 对象操作待处理时触发一个 call 对象操作。一种简单的方法是使用会议状态来更新相关按钮的空闲状态,以便用户在安全之前无法启动操作,就像我们在演示应用中所做的那样。
[4] 由于destroy()是异步的,因此只能在 destroy() 的 Promise 得到解决后才调用 DailyIframe.createCallObject()。
[5] 在演示应用中,我们使用 reducer 来更新组件状态。 [6]如果我们从未收到过参与者的音频或视频轨道,
我们只会将其设置为 true。 [7] 我们这样做是为了在媒体轨道发生变化时 以编程方式设置它们的属性(请参阅Tile.js 中的第 18-31 行)。 [8] 您可能还记得,“participant-joined”和“participant-left”仅与其他(非本地)参与者有关。isLoading
srcObject

文章来源:https://dev.to/trydaily/build-a-video-chat-app-in-minutes-with-react-and-daily-js-481c
PREV
重新创建:Spotify(第 1 部分)你好🌍免责声明第一步将设计分解成更小的部分编写左侧边栏代码💻进一步检查侧边栏悬停动画总结
NEXT
Meteor、Webpack 和 Parcel 对比