序言
话说上一回,我说到了直播和聊天室,使用的是原生实现的。然而对我来说这太简单了,不足以体现我技术的优越性。下面开启我的装逼之旅。
效果
1.截图
2.视频
关键看游客模式,登录提醒,跳转登录,发送缓存消息这些功能
网页聊天室效果
直播实现
直播使用的是乐视的标准直播。为什么使用乐视标准直播呢,因为他提供了推流客户端,为什么要使用它的推流客户端呢,首先减少开发成本,其次也是最重要的我将在本文的最后揭晓谜底。
乐视云地址
乐视云
这里进入标准直播
选择直播活动管理,就可以下载云采集了。
Android和IOS都支持
接着大家可以在手机,或者在控制台创建活动
创建成功就是这样了
在操作选择分析活动,就可以拿到HTML代码了,然后网页直播的功能就实现了。
聊天室实现
聊天室的实现就比较有技术含量了,而基于的SDK是LeanCloud的网页聊天室
文档在这里实时通信开发指南 · JavaScript
我遇到的困难有几点
1.实现游客模式,在用户未登录的情况下可以看到聊天内容
2.如果用户有聊天,点击发送按钮必须判断用户是否登录,给出提示,并弹到相应的界面。
3.用户在游客模式下输入的内容,要保存起来,在登录以后发送出去
4.用户clientID与用户名的处理,clicendID必须唯一,但是用户昵称可以重复。
这里面涉及到JavaScript和Java的交互,我先把接口提供出来。
//用于和JavaScript交互的接口class Mobile {//保存聊天信息@JavascriptInterfacepublic void saveMessage(String msg){Log.i("zzz","saveMessage() msg="+msg);isNeedSendMessage=true;needSendMessage=msg;}//是否需要发送缓存的聊天信息@JavascriptInterfacepublic boolean isNeedSendMessage(){// Log.i("zzz","isNeedSendMessage() isNeedSendMessage="+isNeedSendMessage);return isNeedSendMessage;}//获取缓存的聊天信息@JavascriptInterfacepublic String getNeedSendMessage(){// Log.i("zzz","getNeedSendMessage()");return needSendMessage;}//消失已经发送,重置状态@JavascriptInterfacepublic void messageHaveSend(){// Log.i("zzz","messageHaveSend()");isNeedSendMessage=false;needSendMessage="";}//是否是游客模式@JavascriptInterfacepublic boolean isTouristMode() {return !isLogin();}//获取游客ID@JavascriptInterfacepublic String getTouristID() {return TouristID;}//是否已经登录@JavascriptInterfacepublic boolean isLogin() {return isLogin;}//获取客户端ID@JavascriptInterfacepublic String getClientID() {return clientID;}//弹出警告@JavascriptInterfacepublic void alert(String msg) {Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();}//跳转到登录@JavascriptInterfacepublic void moveToLogin() {needLogin = true;startActivity(new Intent(MainActivity.this, LoginActivity.class));}}
这是和聊天相关的JavaScript代码
//判断是否是游客模式
if(moblie.isTouristMode()){//如果是游客模式的话,获取游客ID并登陆initChatroom(moblie.getTouristID());
}else{//如果不是游客模式,则判断是否登录
if(moblie.isLogin()){//如果已经登录,则获取clientIDinitChatroom(moblie.getClientID());}else{//否则弹出提示moblie.alert("请登录");//跳转到登录界面moblie.moveToLogin();}}//初始化聊天室
function initChatroom(clientID){
angular.module('realTimeModule', []).controller('realTimeCtrl', ['$scope', '$timeout', function($scope, $timeout) {initStatus();initData();function initStatus() {$scope.status = {Realtime: AV.Realtime,appId: "jPbaegow8uVUDUDw1XS7LCv0-gzGzoHsz",region: ['cn', 'us'],//用户名IDclientId:clientID,roomId: "57fcac8ea22b9d005b0e9884",client: "",isLinkSuccess: false,isLinkFailed: false,isLinking: false,messageIterator: "",msg: "",room: "",myMsgs: [],members: [],historys: []};}function initData() {$scope.data = {realtime: new $scope.status.Realtime({appId: $scope.status.appId,region: $scope.status.region[0],}),};initChatroom();setVideoScale();setChatroomHeight();}/*** [initChatroom description] 初始化聊天室* @return {[type]} [description]*/function initChatroom() {$scope.status.isLinking = true;$scope.data.realtime.createIMClient($scope.status.clientId).then(function(client) {$timeout(function() {$scope.status.isLinkSuccess = true;$scope.status.client = client;}, 500);client.on('disconnect', function() {$scope.status.isLinkFailed = true;});return client.getConversation($scope.status.roomId);}).then(function(conversation) {if (conversation) {return conversation;} else {return $scope.status.client.createConversation({name: "chatRoom",members: [],});}}).then(function(conversation) {$scope.status.roomId = conversation.id;return conversation;}).then(function(conversation) {$scope.status.members = conversation.members;return conversation;}).then(function(conversation) {return conversation.join();}).then(function(conversation) {// 获取聊天历史$scope.status.room = conversation;conversation.queryMessages({limit: 50,}).then(function(messages) {var newMessages=new Array();for(var i=0, len=messages.length; i<len; i++){var message=messages[i];handleName(message);newMessages.unshift(message);}$scope.status.historys = newMessages;});conversation.on('message', function(message) {$timeout(function() {handleName(message);$scope.status.historys.unshift(message);});});//发送游客登录时未发送的消息if(moblie.isNeedSendMessage()){var msg= moblie.getNeedSendMessage();$scope.status.msg=msg;//sendMessage();sendMsg();moblie.messageHaveSend();moblie.alert("评论成功");}});}function handleName(message){var from=message.from;var index=from.indexOf("#");if(index!=-1){from=from.substr(index+1);}message.from=from;// return message;}/*** [sendMsg description] 发送消息* @return {[type]} [description]*/function sendMsg() {if(!moblie.isLogin()){moblie.saveMessage($scope.status.msg);moblie.alert("请登录");moblie.moveToLogin();return;}$scope.status.room.send(new AV.TextMessage($scope.status.msg)).then(function(message) {$timeout(function() {message.from="自己";$scope.status.historys.unshift(message);});$scope.status.msg = '';});}/*** [linkToServer description] 连接到服务器* @return {[type]} [description]*/$scope.linkToServer = function() {initChatroom();};/*** [sendMessage description] 发送消息* @return {[type]} [description]*/$scope.sendMessage = function(e) {if ((angular.isDefined(e) && e.keyCode == 13) || angular.isUndefined(e)) {sendMsg();}};/*** [setVideoScale description] 根据16/9的比例设置视频高度*/function setVideoScale() {var cw = document.documentElement.clientWidth;var scale = 16 / 9;var vh = cw / scale;var video = document.getElementById("player");video.style.height = vh + "px";}/*** [setChatroomHeight description] 设置聊天室的高度(为了移动端一屏展示)*/function setChatroomHeight() {var video = document.getElementById("player");var chatroomHeader = document.getElementById("chatroomHeader");var chatroom = document.getElementById("chatroom");var sendBox = document.getElementById("sendBox");var ch = document.documentElement.clientHeight;var vh = getStyle(video).height;var crheader = getStyle(chatroomHeader).height;var sbh = getStyle(sendBox).height;var crh = parseInt(ch) - parseInt(vh) - parseInt(crheader) - parseInt(sbh);if (crh < 1) {crh = crh + 500;}console.log(crh);chatroom.style.height = crh + "px";chatroom.style.marginBottom = sbh;}function getStyle(ele) {var style = null;if (window.getComputedStyle) {style = window.getComputedStyle(ele, null);} else {style = ele.currentStyle;}return style;}window.onresize = function() {setVideoScale();setChatroomHeight();};}]);}
1.游客模式
实现游客模式的逻辑如下,这是聊天室相关的JavaScript代码。
而游客ID的获取如下,通过拼接两个随机数来实现。
2.登录判断
看关键的JS代码,主要就是在发送消息的时候判断登录状态。
/*** [sendMsg description] 发送消息* @return {[type]} [description]*/function sendMsg() {//判断是否登录if(!moblie.isLogin()){//未登录,则保存输入框中的消息moblie.saveMessage($scope.status.msg);//弹出提示moblie.alert("请登录");//跳转到登录界面moblie.moveToLogin();return;}$scope.status.room.send(new AV.TextMessage($scope.status.msg)).then(function(message) {$timeout(function() {message.from="自己";$scope.status.historys.unshift(message);});$scope.status.msg = '';});}
如果需要登录则跳转到登录界面
//跳转到登录@JavascriptInterfacepublic void moveToLogin() {//表示需要登录needLogin = true;startActivity(new Intent(MainActivity.this, LoginActivity.class));}
LoginActivity如下
public class LoginActivity extends AppCompatActivity {EditText et_name;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);et_name = (EditText) findViewById(R.id.et_name);}public void login(View view) {String name = et_name.getText().toString();MainActivity.isLogin = true;MainActivity.clientID = name;Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();finish();}
}
回来以后在聊天室界面的onResume方法中判断是否需要登录,如果需要通过 webView.reload()重新加载网页。
@Overrideprotected void onResume() {super.onResume();if (needLogin) {webView.reload();needLogin = false;}}
网页每次加载都会执行这个方法,这里就会进入到登录的逻辑了。
//判断是否是游客模式
if(moblie.isTouristMode()){//如果是游客模式的话,获取游客ID并登陆initChatroom(moblie.getTouristID());
}else{//如果不是游客模式,则判断是否登录
if(moblie.isLogin()){//如果已经登录,则获取clientIDinitChatroom(moblie.getClientID());}else{//否则弹出提示moblie.alert("请登录");//跳转到登录界面moblie.moveToLogin();}}
3.保存未发送信息
1.在发送消息的时候,如果没有登录,则通过Java接口保存消息
/*** [sendMsg description] 发送消息* @return {[type]} [description]*/function sendMsg() {//判断是否登录if(!moblie.isLogin()){//未登录,则保存输入框中的消息moblie.saveMessage($scope.status.msg);//弹出提示moblie.alert("请登录");//跳转到登录界面moblie.moveToLogin();return;}$scope.status.room.send(new AV.TextMessage($scope.status.msg)).then(function(message) {$timeout(function() {message.from="自己";$scope.status.historys.unshift(message);});$scope.status.msg = '';});}
在登录聊天室成功以后,发送保存的信息
//发送游客登录时未发送的消息if(moblie.isNeedSendMessage()){//获取保存的消息var msg= moblie.getNeedSendMessage();$scope.status.msg=msg;//发送消息sendMsg();//重置状态moblie.messageHaveSend();//提示用户moblie.alert("评论成功");}
4.用户ID
这个处理我用在实际的项目中了,这Demo没有,大致的说一样原理
为了实现ClientID唯一,我选择了使用LeanCloud的ObjectID做ClientID但是现实的效果是
一大堆的数字字母串,聊天的时候就这这样了,没有显示用户昵称,效果和差。
aojogo80900nfjf3883949:我是李四
oiigjiooiijglle9939948:我是张三,你的名字好奇怪
于是我使用了ObjectID:昵称 这种形式。比如
aogoijsojgoij5651211sdjk:李四
在获取到消息以后就只截取后面的昵称用来显示
李四:我是李四
张三:我是张三
效果很不错,而且解决了唯一性和易用性的问题,我在真实项目中已经实现了,所以大家也不要怀疑是实用性了。
源码
结构
realtime.html是聊天室界面
realtime.js是聊天室逻辑
大家只需要替换,AppID,和roomID为你们自己的就可以用在自己的项目中了
关于弹幕,在获取到消息以后实用JavaScript以弹幕的方式显示就行了。
下载
下载地址
总结
这个项目主要的难度是考察了我Java和JavaScript的交互能力,和我的JavaScript的水平。realtime.js中的所有和客户端交互的逻辑都是我自己写的,这里需要重点表扬一下。还有一点可以说明一下,通过这种方式,我们的IOS也可以实现一样的效果,所有小伙伴们不要害怕,如果你们的IOS端做不出来,只能说明水平太菜,一个小插曲就是我的Mobile这个单词写错了,已改是mobile,不过无关大雅。然后说说我们会什么要使用网页实现直播和聊天室,为什么我要在这儿装逼,全都是被逼的,在上一篇博客结束以后,我们告诉客户我们准备用百度云SDK,客户说:”什么!我们乐视云都已经付费了,必须用乐视“。在上一篇文章我就说过乐视云根本不能用,客户不信,于是就有了下面这个。
还有这个
由于移动直播根本不能用,于是改用标准直播,还有他们官方的推流工具,但是官方的推流工具在Android端依然有兼容性问题。
奉劝大家,真心不要用乐视,这是我曾经的一些坑,如果能帮到你,那我这个逼也就没有白装了。