游戏直播是Discord产品的核心功能之一,本教程教大家如何1天内开发一款内置游戏直播的国产版Discord应用,用户不仅可以通过IM聊天,也可以进行语聊,看游戏直播,甚至自己进行游戏直播,无任何实时音视频底层技术的Web开发者同样适用,效果如下:
本项目基于环信超级社区的实例项目, 所以我们先从Circle-Demo-Web这个仓库开启做初始化
运行后, 登录完毕效果如下,

- 个人信息页
 - 好友会话页
 - 当前加入的频道
 - 创建新频道
 - 加入服务器
 
社区(Server)、频道(Channel) 和子区(Thread) 三层结构
社区为一个独立的结构, 不同社区直接相互隔离, 社区包含不同的频道, 代表了不同的话题, 用户在频道中聊天, 而针对一条单独信息产生的回复为子区.
我们本次的项目主要集中在频道部分, 需要加入一个服务器后, 创建一个测试社区, 保证你具有管理员权限.
我们的目标是尽量利用现有api扩展功能, 有几个问题需要解决
- 如何区分普通频道和游戏频道?
 - 如何区分当前频道是否有玩家直播, 如果有直播如何获取玩家信息, 第二玩家的状态?
 - 多人聊天的状态?
 
这里直接简单采用频道前缀做特殊区分, 创建频道前缀带video-的识别为游戏频道, 同时将渲染内容做替换
const isVideoChannel = useMemo(() => {
	return currentChannelInfo?.name?.startsWith("");
}, [currentChannelInfo]);
const renderTextChannel = () => {
return (
  <>
	<MessageList
	  messageInfo={messageInfo}
	  channelId={channelId}
	  handleOperation={handleOperation}
	  className={s.messageWrap}
	/>
	<div className={s.iptWrap}>
	  <Input chatType={CHAT_TYPE.groupChat} fromId={channelId} />
	</div>
  </>
);
}
const renderStreamChannel = () => {
return (
	<>This is a Stream Channel<>
);
}
return (
	...
	<div className={s.contentWrap}>
		{isVideoChannel ? renderStreamChannel() : renderTextChannel()}
	</div>
	...
);
如果需要区分图标, 可以搜索channelNameWrap, 分别在channelItem和Channel/components/Header中添加一个css类, 通过这个类设置图标图片
如何区分当前频道是否有玩家直播, 如果有直播如何获取玩家信息, 第二玩家的状态?
我们可以复用在频道中发送消息的机制, 直播开始, 结束都可以当做一条特殊的消息发送, 只不过这条消息不承载用户的信息, 而是表达用户上下播的行为
当然这个机制存在一定实时性的问题, 不过大致是可行的.
  
 
  const sendMessage = useCallback(() => {
    if (!text) return;
    getTarget().then((target) => {
      let msg = createMsg({
        chatType,
        type: "txt",
        to: target,
        msg: convertToMessage(ref.current.innerHTML),
        isChatThread: props.isThread
      });
      setText("");
      deliverMsg(msg).then(() => {
        if (msg.isChatThread) {
          setThreadMessage({
            message: { ...msg, from: WebIM.conn.user },
            fromId: target
          });
        } else {
          insertChatMessage({
            chatType,
            fromId: target,
            messageInfo: {
              list: [{ ...msg, from: WebIM.conn.user }]
            }
          });
          scrollBottom();
        }
      });
    });
  }, [text, props, getTarget, chatType, setThreadMessage, insertChatMessage]);
去除掉与输入框逻辑耦合的部分, 可以分为两步, createMsg创建消息, deliverMsg发送消息, 这两个功能都是环信SDK功能的封装, 经过查阅文档, 它支持发送自定义消息.
在utils中新建一个stream.js文件来封装直播的逻辑
const sendStreamMessage = (content, channelId) => {
  let msg = createMsg({
    chatType: CHAT_TYPE.groupChat,
    type: "custom",
    to: channelId,
    ext: {
      type: "stream",
      ...content,
    },
  });
  return deliverMsg(msg)
    .then(() => {
      console.log("发送成功");
    })
    .catch(console.error);
};
它接收content表示我们的额外信息, 用户名和上下播状态, channelId区分不同的channel, 对它的调用可以如下
const CMD_START_STREAM = "start";
const CMD_END_STREAM = "end";
sendStreamMessage(
	{
	  user: userInfo?.username,
	  status: CMD_START_STREAM,
	},
	channelId
);
sendStreamMessage(
  {
	user: userInfo?.username,
	status: CMD_END_STREAM,
  },
  channelId
);
第二玩家的状态可以类比第一个玩家用额外的自定义消息实现, 这里不做重复.
关于自定义消息, 原本它的作用是邀请用户加入频道, 你可以在components/CustomMsg中找到, 我们要额外识别一下直播消息(可以渲染在消息列表里, 也可以直接屏蔽掉).
const isStream = message?.ext?.type === "stream";
const renderStream = () => {
return (<>)
}
if (isStream) {
	return renderStream();
} else {
	...
}
我们引入声网RTC sdk, 每个进入直播房间的用户都对应维护一个声网客户端,
通过on事件感知远端视频/音频流.
- 注册声网开发者, 并在后台创建一个测试项目
 - 项目根目录创建
.env文件, 存放api token等信息 
REACT_APP_AGORA_APPID = your app id
REACT_APP_AGORA_CHANNEL = test 
REACT_APP_AGORA_TOKEN = your token
REACT_APP_AGORA_UID = 123xxx
- 添加声网sdk依赖 
npm install agora-rtc-sdk-ng