使用声网 Web SDK 为直播推流应用创建“举手”功能
举手示意主持人,请求上麦。
作为一名社区驱动型的开发人员,我会通过直播推流应用举行教育研讨会。我通常会在每次会话快结束的时候留一些 Q&A 时间,然后我发现 Q&A 时间总是进展的无比艰难,原因是经常会有好几个参与者同时在会议聊天窗口发消息提问,让我很难记清楚每个人的具体问题,更不用说跟他们进一步沟通了。
所以,我思来想去,觉得解决这个问题的最好办法是与参与者直接对话。
下面这个教程会教大家开发一个网页版应用,在这个应用里,用户可以发送角色升级请求,主持人有权接受或拒绝这个请求。一旦主持人同意用户的请求,用户就会成为直播推流应用里的发言者。下面我们使用(直播 SDK) 声网 Web SDK 和 声网 RTM SDK 来搭建样例 app:
前期准备
- 了解 JavaScript、JQuery、Bootstrap和 Font Awesome 的基础知识
- 声网开发者帐户(详细步骤可参考这篇文章)。
- 了解如何使用 声网 Web SDK 和 声网 RTM SDK
项目设置
首先,我们克隆这个项目的 GitHub 资源库,得到一个下面这样的项目:
声网多人视频直播推流应用的用户界面截图
如果无法理解上面的代码,请查看这个教程。我已经在教程里添加了一个代码,用于在用户界面和 JavaScript 文件中输入用户名。总之,现在我们有了一个全功能视频直播推流应用,而且具有静音和取消静音的功能。
有人举手,我该怎么操作?
无论用户举手或放下手,我们都会使用 声网 RTM SDK 发送频道消息。如果同时有多位主持人,则所有主持人都会收到频道消息,任一主持人都可以选择升级该用户角色或拒绝其请求。
我们根据观众(用户角色)的点击按钮事件来进行相应的操作。如果有观众点击了 举手 按钮,我们就触发该按钮的信息和功能。同时,我们也更新我们创建的全局变量的状态,实时记录用户是否处于举手状态。
// Hand raise state
var handRaiseState = false;
...
// Append a raise hand button for audience (hosts cannot raise their hand)
async function join() { // create Agora client
client.setClientRole(options.role);
$("#mic-btn").prop("disabled", false);
$("#video-btn").prop("disabled", false);
if (options.role === "audience") {
$("#mic-btn").prop("disabled", true);
$("#video-btn").prop("disabled", true);
$("#raise-hand-div").append(`<button id="raise-hand" type="button" class="btn btn-live btn-sm" disabled>Raise Hand</button>`);
// Event listeners
client.on("user-published", handleUserPublished);
client.on("user-joined", handleUserJoined);
client.on("user-left", handleUserLeft);
}
...
}
...
// RTM Channel Join
var channelName = $('#channel').val();
channel = clientRTM.createChannel(channelName);
channel.join().then(() => {
console.log('AgoraRTM client channel join success.');
// Send channel message for raising hand
$(document).on('click', '#raise-hand', async function () {
fullDivId = $(this).attr('id');
if (handRaiseState === false) {
$("#raise-hand").text("Lower Hand");
handRaiseState = true;
console.log("Hand raised.");
// Inform channel that rand was raised
await channel.sendMessage({ text: "raised" }).then(() => {
console.log("Message sent successfully.");
console.log("Your message was: raised" + " sent by: " + accountName);
}).catch((err) => {
console.error("Message sending failed: " + err);
})
}
else if (handRaiseState === true) {
$("#raise-hand").text("Raise Hand");
handRaiseState = false;
console.log("Hand lowered.");
// Inform channel that rand was raised
await channel.sendMessage({ text: "lowered" }).then(() => {
console.log("Message sent successfully.");
console.log("Your message was: lowered" + " sent by: " + accountName);
}).catch((err) => {
console.error("Message sending failed: " + err);
})
}
});
// Get channel message when someone raises hand
channel.on('ChannelMessage', async function (text, peerId) {
console.log(peerId + " changed their hand raise state to " + text.text);
if (options.role === "host") {
if (text.text == "raised") {
// Ask host if user who raised their hand should be called onto stage or not
if (confirm(peerId + " raised their hand. Do you want to make them a host?")) {
// Call user onto stage
console.log("The host accepted " + peerId + "'s request.");
// Send approval message
} else {
// Inform the user that they were not made a host
console.log("The host rejected " + peerId + "'s request.");
// Send rejection message
}
} else if (text.text == "lowered") {
console.log("Hand lowered so host can ignore this.");
}
}
})
}).catch(error => {
console.log('AgoraRTM client channel join failed: ', error);
}).catch(err => {
console.log('AgoraRTM client login failure: ', err);
});
});
...
收到用户请求之后,我怎么同意或拒绝呢?
因为我们只打算对发送请求的用户进行升级或拒绝操作,所以我们向用户发送点对点消息来更新同意和拒绝状态。
如果用户的角色升级请求被同意,用户会收到成功把角色更改为主持人的点对点消息,随后该用户会以主持人角色重新加入频道。
...
// Get channel message when someone raises hand
channel.on('ChannelMessage', async function (text, peerId) {
console.log(peerId + " changed their hand raise state to " + text.text);
if (options.role === "host") {
if (text.text == "raised") {
// Ask host if user who raised their hand should be called onto stage or not
$('#confirm').modal('show');
$('#modal-body').text(peerId + " raised their hand. Do you want to make them a host?");
$('#promoteAccept').click(async function () {
// Call user onto stage
console.log("The host accepted " + peerId + "'s request.");
await clientRTM.sendMessageToPeer({
text: "host"
},
peerId,
).then(sendResult => {
if (sendResult.hasPeerReceived) {
console.log("Message has been received by: " + peerId + " Message: host");
} else {
console.log("Message sent to: " + peerId + " Message: host");
}
}).catch(error => {
console.log("Error sending peer message: " + error);
});
$('#confirm').modal('hide');
});
$("#cancel").click(async function () {
// Inform the user that they were not made a host
console.log("The host rejected " + peerId + "'s request.");
await clientRTM.sendMessageToPeer({
text: "audience"
},
peerId,
).then(sendResult => {
if (sendResult.hasPeerReceived) {
console.log("Message has been received by: " + peerId + " Message: audience");
} else {
console.log("Message sent to: " + peerId + " Message: audience");
}
}).catch((error) => {
console.log("Error sending peer message: " + error);
});
$('#confirm').modal('hide');
});
} else if (text.text == "lowered") {
$('#confirm').modal('hide');
console.log("Hand lowered so host can ignore this.");
}
}
})
// Display messages from host when they approve the request
clientRTM.on('MessageFromPeer', async function ({
text
}, peerId) {
console.log(peerId + " changed your role to " + text);
if (text === "host") {
await leave();
options.role = "host";
console.log("Role changed to host.");
await client.setClientRole("host");
await join();
$("#host-join").attr("disabled", true);
$("#audience-join").attr("disabled", true);
$("#raise-hand").attr("disabled", false);
$("#leave").attr("disabled", false);
} else if (text === "audience" && options.role !== "audience") {
alert("The host rejected your proposal to be called onto stage.");
$("#raise-hand").attr("disabled", false);
}
})
}).catch(error => {
console.log('AgoraRTM client channel join failed: ', error);
}).catch(err => {
console.log('AgoraRTM client login failure: ', err);
});
});
...
我们已经把应用程序的架构好了,现在可以进行测试~
注意: 测试的时候,可以使用2个或以上浏览器选项卡来模拟通话中的多个用户。
总结
大功告成啦!
根据上面的教程,我们成功在网页直播推流应用中添加了基于请求的角色升级服务。有些读者想看到配置好的应用,但没法跟我一起编码,所以我已经把所有的(直播SDK)代码都上传到了 GitHub。
感谢阅读这篇教程的童鞋们~如果大家有任何问题,可以在下方评论。如果觉得有可以改进的地方,欢迎随时复刻这个仓库并创建拉取请求!
其他资源
- 想了解更多关于声网 Web SDK (直播SDK)和其他用例的信息,请点击 这里 查看开发者指南。
原文作者:Akshat Gupta Akshat Gupta 是声网Agora 的实习开发人员,主攻前端 web 技术,致力于帮助开发人员把声网Agora Web SDK 集成进各种应用中,为此写了不少文章,也开发了多个项目。
原文链接:Building a Raise-Your-Hand Feature for Live Streams Using the Agora Web SDK