创建一个结合远程和现场体验的 WebRTC 应用程序不仅越来越有必要,而且也不像听起来那么简单。成功的混合方法不仅仅是改变我们工作或学习的地点。它还改变了工具,以及我们使用工具的方式。我们的目标是两全其美。
正确做到这一点的诀窍在于考虑到远程方法中被证明成功的工作流程,并将其与现场实践相结合,使两者相互适应。在本文中,我们将以在线教育工具为例,探讨如何打造这样的体验。
WebRTC 和远程功能
WebRTC 使用双重方法来实现这一点。
- 它促进所有参与者之间建立音频/视频通信
- WebRTC 的 DataChannel API 可以交换任意数据,支持聊天、虚拟板或任何意味着实时信息同步的高级功能
有多种方法可以将 WebRTC 功能实现到 Web 应用程序中:
- 直接使用浏览器中可用的 API 并手动建立点对点连接
- 利用开源媒体服务器
- 通过通信平台即服务 (CPaaS) 提供商的服务
为简单起见,在本示例中,我们将使用 Vonage Video API。代码示例使用 Next.js,依靠 GraphQL 订阅进行实时更新,并使用 Recoil 状态管理在客户端存储这些更新。但是,相同的概念可以应用于一般的 Web 开发和 pubsub 机制(例如WebSockets)。
第一步:通过在 Next.js 中设置 Vonage Video API 添加远程功能
让我们看看如何开始使用 Vonage Video API 为我们的应用程序添加远程功能。首先,我们需要在后台安装 OpenTok 软件包。然后,我们需要添加创建新会话的方法。为此,我们使用 GraphQL 定义一个mutation事件,如下所示。
在mutation事件中,我们使用新创建的会话 ID 和状态更新模型对象,并通知前端会话已启动。可以使用一个简单的 pubsub 机制来实现此目的,但我们不会将其作为本文的一部分进行介绍,因为它超出了本文的范围。
// GraphQL mutation for creating a session
startClass: async (_, { id }) => {
// initialize Opentok with credentials
// for security, credentials should be loaded dynamically
// i.e. using environment variables
const opentok = new OpenTok(
process.env.VONAGE_API_KEY,
process.env.VONAGE_API_SECRET
);
try {
return new Promise((resolve, reject) => {
// create the session
opentok.createSession({ mediaMode: "routed" }, (err, session) => {
if (err) {
return reject(err);
}
// if everything goes well then we update the session's model object
// this will save the session information in the database
Class.update(
{
vonage_session: session?.sessionId,
status: "in_progress",
},
{ where: { id: id }, returning: true, plain: true }
).then((response) => {
// after creating the session we notify the frontend
pubSub.publish("classStarted");
return resolve(response[1].dataValues);
});
});
});
} catch (err) {
throw new GraphQLError(err);
}
};
接下来,在前端,我们需要初始化会话,以便学生和教师可以连接。为此,我们利用Next.js 中的getServerSideProps函数在页面呈现之前在服务器中设置所需的值。在那里,我们从后端查询会话,初始化OpenTok 的客户端库并生成用户加入通话的令牌。最后,我们将会话 ID 和令牌作为 props 传递给页面。
export const getServerSideProps: GetServerSideProps = async ({
params,
req,
}) => {
...
// same as the backend we get credentials from
// server-side environment variables
const apiKey = process.env.VONAGE_API_KEY!;
const apiSecret = process.env.VONAGE_API_SECRET!;
...
// we query the backend to get the session.
// In this example we use assume there is a GraphQL query
// that performs a search in the database
const { data } = await client.query({
query: GetClassBySlugWithUserDocument,
variables: { // the query params },
});
...
// we obtain the data from query results including session data
const { getClassBySlug: thisClass, ... } = data;
// we initialize client side Opentok library
const otClient = new OpenTok(apiKey, apiSecret);
let sessionToken = null;
let sessionId = thisClass?.vonage_session || null;
...
// we generate a token for the user to join the session
if (sessionId && otClient) {
sessionToken = await otClient.generateToken(sessionId, {
expireTime: new Date().getTime() / 1000 + 6 * 60 * 60,
role: "moderator",
data: JSON.stringify(viewer),
});
}
// we pass the session information as props to the page
return {
props: {
sessionToken,
sessionId,
thisClass: data?.getClassBySlug,
...
},
};
};
现在,您可以从客户端代码使用此数据来加入会话。在整个应用程序中提供此信息的一个好方法是实现状态管理工具,例如 Recoil。然后只需初始化并连接会话即可,如下所示:
const session = Opentok.initSession(process.env.VONAGE_API_KEY, sessionId);
第二步:设置类“举手”功能
当所有老师和学生都在同一个教室时,学生想要提问或发表意见可以举手,请老师允许他们发言。这是基于老师看到学生的能力。
即使学生视频流可用,也很难通过远程方法对其进行跟踪。实施一种机制使举手更加明确非常重要。当学生“举手”时,特定的 UI 元素必须突出显示或发出声音。
实现这一点的第一步是存储远程学生的“状态”,其中包括他/她的位置和举手旗等信息。然后,在客户端,我们需要对这种状态的任何变化做出“反应”。
为此,我们使用 GraphQL 订阅并将值存储在 Recoil 状态中。下面是一个使用 Apollo 客户端库执行订阅的示例。
...
// return a setter variable to write into the state
const setUserStatusOnline = useSetRecoilState(userStatusAtom);
// execute a previously defined GraphQL subscription
// that listens for changes in student status
useSubscription(MY_CLASS_USER_STATUS, {
...
// on receiving data
onData: ({ data: subscriptionData }) => {
const { data } = subscriptionData;
// get student's data
const {
status,
location,
role,
handRaised,
userMode,
isMobile,
... // more properties
} = data?.getMyClassUserStatus ?? {};
// store data in the state
setUserStatusOnline({
status,
location,
role,
handRaised,
userMode,
isMobile,
...
});
},
});
除了监听变化之外,我们还需要触发这样的变化。为此,我们创建了一个切换函数,该函数将改变举手的状态,从而使订阅听到此更改。
const toggle = (userId: null, raised) => {
upsertClassUser({
variables: {
input: {
userId: userId || viewer?.id,
handRaised: raised,
classSlug: slug as string,
},
},
});
}
然后,我们创建一个Recoil 选择器来确定学生是否举手。接下来,我们使用该选择器有条件地渲染 UI 元素。
// We have a recoil selector that will trigger hand raised change
export const handRaisedSelector = selector({
key: "handRaisedSelectorKey",
get: ({ get }) => {
const userStatusOnline = get(userStatusAtom);
return userStatusOnline.handRaised;
},
cachePolicy_UNSTABLE: {
eviction: "most-recent",
},
});
...
// And the HandRaisedButton that reflects the change
const HandRaisedButton = () => {
const handRaised = useRecoilValue(handRaisedSelector);
const { toggle } = useToggleHandRaised();
return (
<StyledDeviceButton
onClick={() => toggle(null, !handRaised)}
name="hand-button"
>
<HandIcon
color={handRaised ? "black" : "white"}
width="25px"
height="25px"
/>
</StyledDeviceButton>
);
};
第三步:适应混合体验
如前所述,采用混合方法需要结合远程和现场体验的最佳方面。我们将虚拟工具引入现场教室,但进行了一些重要的调整。这使组织能够创建一个多功能、动态的环境,适应不同的场景并优化生产力和协作。
在我们的远程教育示例中,成功的混合方法使现场和远程学生都可以使用应用程序的核心功能。我们只是改变沟通方式。
到目前为止,该应用程序已通过提供视频会议和“举手”功能针对远程方法进行了优化。现在,我们希望:
- 为这两种类型的学生实施共享白板/笔记本等功能。(请记住,“保持应用程序的核心功能对于两种类型的用户都是通用的”。)
这非常适合 WebRTC 的 DataChannel API 实时同步功能。 - 为现场学生删除与视频会议相关的用户界面元素,因为他们可以在没有视频会议的情况下直接与老师交谈。现场学生将使用该应用程序的精简版本,其中不包含与视频/音频会议相关的 UI 元素。
- 调整物理空间,为远程学生提供与现场学生相同的相关性。
后者是采用混合方法时经常被遗忘的一个方面。在我们的文章“WebRTC和实时通信在体育领域的应用”中,我们解释了他们如何通过添加屏幕这样简单的事情来实现远程和现场协作,这些屏幕可以让不在场的球队主席看到,关于联赛的讨论。
回到我们的远程教育应用程序……考虑到 Next.js 应用程序基于组件的方法,网页的所有元素都可以构建为可重用的“组件”。这允许您使用它们构建多个页面,而无需重写常见的 UI 元素。
因此,为现场和远程学生构建单独的会话页面相当简单,仅包含每个学生所需的内容。
为了在远程学生的会话页面和等候大厅以及现场学生的“混合”会话页面之间进行切换,我们有一个位置管理器来监听用户状态,包括位置和模式。
// Recoil selectors that determine values of student status
const userLocation = useRecoilValue(locationSelector);
const isHybrid = useRecoilValue(hybridSelector);
... // more selectors
useEffect(() => {
...
// determine if a users is joining on-site (hybrid) or remotely
const newUserMode = userLocation === "class" && isHybrid ? "/hybrid" : "";
// build the url of a class identified by a slug
// when joining remotely, put the student in a waiting room (lobby)
const newRoute = `/class/${slug}${
userLocation === "class" ? "" : "/lobby"
}${newUserMode}`;
// move the student to the proper page
if (newRoute !== router.asPath) {
if (userLocation === "class") {
CookieService.set("granted_access", slug as string, {
path: "/",
expires: new Date(Date.now() + 7200000),
});
}
window.location.href = newRoute;
}
}, [isHybrid, router.asPath, slug, userLocation, userRoleSelector]);
现在你就拥有了!
通过利用 Vonage Video API 的强大功能,结合用于基于组件的视图设计的 Next.js 框架、用于实时侦听变化的 GraphQL 订阅以及用于本地存储这些变化的 Recoil 状态管理,我们优化了教育技术应用程序,将远程学生与教师连接起来。我们还为现场的学生进行了调整,使他们都能获得类似的体验。
混合方式可以将现场的社会效益与远程方式的灵活性结合起来,是教育和医疗保健等许多行业的便捷选择。不过,同样重要的是要正确塑造工作流程,包括支持这种方法的物理空间和软件工具。
作者:Andrea Phillips
原文:https://webrtc.ventures/2023/08/building-hybrid-webrtc-experiences-edtech-case-study/
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/32801.html