因为博主是个忠实的英雄联盟粉丝,因此常常观看一些明星大神的直播。而一谈到直播,确定会看到满屏幕飘来飘去的弹幕。那么问题来了,这些视频弹幕网站如何作到实时同步的?PHP如何开发一个相似的网站?javascript
首先要搞定的是前端页面,最起码得有个框,让弹幕飞起来吧。一想到前台,博主头就大(毕竟我不喜欢去扣前端代码,并且作出来的东西还巨丑)。那我们就百度一下吧,看看有什么好用的弹幕插件,如今开源的东西那么多。php
通过搜索,找到了一个jQuery.danmu.js的开源项目。看了一下star的人还挺多。https://github.com/chiruom/jquery.danmu.jscss
因而乎,管他三七二十一,先down下来再说。html
git clone https://github.com/chiruom/jquery.danmu.js.git
大体一看目录结构以下:前端
进入demo目录,先运行一下例子看看结果呗。java
果真,点开之后出现了一个高大上的页面,略看一下功能还挺多。可是问题来了,为啥我点击开始,一点反应也木有呢。jquery
寻找缘由ing。git
原来是源文件中的jQuery插件的问题。在src目录下,并无该文件github
<script src="../src/jquery-2.1.4.min.js"></script>
算了仍是调用百度的在线jQuery插件吧web
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
再一刷新,不出预料,成功运行。
颇有意思,有木有,很激动有木有。然而重点才刚刚开始。
后端,那就先来讲说弹幕的原理吧。弹幕,就至关于一个公共聊天室,都是一个客户端发送消息给服务端,服务端再将收到的消息广播给其余的客户端。
用传统的ajax轮询吗?不行,这样效率过低,想一想各大火爆的直播平台都是同一时间几万人在线,几千人同时发弹幕,若是靠ajax轮询一个PHP接口的话服务器会吃不消的。且弹幕消息存储方案略显复杂,有人问为何要存储呢?由于ajax使用的HTTP协议是无状态协议,A客户端和B客户端之间对于服务器来讲没有任何标志,若是服务器要确保A客户端和B客户端分别在两次请求的时候服务器只返回这两个客户端没有获取过的弹幕消息,那么服务器端就必须使用一个缓存来标识某某客户端看过哪条弹幕消息。综上所述ajax能够实现小规模的弹幕通讯方案,可是很麻烦。
好在最新的HTML5中加入了WebSocket协议,咱们能够通WebSocket这种基于HTTP协议之上的即时通讯协议来替代ajax这种传统的我问你答的老旧通讯模式。而咱们是PHPer,对于咱们这种只懂PHP的人该如何编写WebSocket服务端呢?好在咱们又得知PHP有一个Swoole扩展,咱们在PHP语言中使用它能够很方便的构建一个WebSocket服务端。
关于Swoole,下面这段是其官网上的话:
PHP的异步、并行、高性能网络通讯引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库链接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询。 Swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端。
Swoole能够普遍应用于互联网、移动通讯、企业软件、云计算、网络游戏、物联网(IOT)、车联网、智能家居等领域。 使用PHP+Swoole做为网络通讯框架,可使企业IT研发团队的效率大大提高,更加专一于开发创新产品。
跟详细的东西请自行参考官网文档。这里就不在废话了。
http://wiki.swoole.com/wiki/page/479.html
还有一个问题须要解决,那就是,这个jquery.danmu.js是基于弹幕运行时间的一个插件。那又要如何作到实时呢。开始博主想的是在服务器端规定一个时间(即其链接时间),当有客户端链接时,返回服务器的当前时间戳,而后以此为依据开始计时。可是遇到的问题以下:
好吧,博主走弯路子了(没作过这方面的东西,缺少经验)。这个时候,就须要转变一种思路了。
websocket是实时通讯的,哎,那全部客户端的时间,不一致就不一致吧,弹幕发的时间根据各个客户端的为准呗,都以当前各个客户端的时间来发,websocket只传递不包含时间的数据(好吧有点绕,我本身都感受说饶了),我们直接来上代码吧。
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>弹幕made by diligentyang</title> <style> body { font-family: "Microsoft YaHei" ! important; font-color:#222; } pre { line-height: 2em; font-family: "Microsoft YaHei" ! important; } h4 { line-height: 2em; } #danmuarea { position: relative; background: #222; width:800px; height: 445px; margin-left: auto; margin-right: auto; } .center { text-align: center; } .ctr { font-size: 1em; line-height: 2em; } </style> <script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script> <script src="../dist/jquery.danmu.min.js"></script> </head> <body class="center"> Demo<br><br> <!--黑背景和弹幕区--> <div id="danmuarea"> <div id="danmu" > </div> </div> <!--控制区--> <div class="ctr" > <button type="button" onclick="pauser()">弹幕暂停</button> <button type="button" onclick="resumer() ">弹幕继续</button> 显示弹幕:<input type='checkbox' checked='checked' id='ishide' value='is' onchange='changehide()'> 弹幕透明度: <input type="range" name="op" id="op" onchange="op()" value="100"> <br> 当前弹幕运行时间(秒):<span id="time"></span> <!--设置当前弹幕时间(秒): <input type="text" id="set_time" max=20 /> <button type="button" onclick="settime()">设置</button>--> <br> 发弹幕: <select name="color" id="color" > <option value="white">白色</option> <option value="red">红色</option> <option value="green">绿色</option> <option value="blue">蓝色</option> <option value="yellow">黄色</option> </select> <select name="size" id="text_size" > <option value="1">大文字</option> <option value="0">小文字</option> </select> <select name="position" id="position" > <option value="0">滚动</option> <option value="1">顶端</option> <option value="2">底端</option> </select> <input type="textarea" id="text" max=300 /> <button type="button" onclick="send()">发送</button> </div> <script> //WebSocket var wsServer = 'ws://123.206.61.229:9505'; var websocket= new WebSocket(wsServer); websocket.onopen = function (evt) { console.log("Connected to WebSocket server."); /*websocket.send("gaga");*/ //连上以后就打开弹幕 $('#danmu').danmu('danmuResume'); }; websocket.onclose = function (evt) { console.log("Disconnected"); }; websocket.onmessage = function (evt) { console.log('Retrieved data from server: ' + evt.data); var time = $('#danmu').data("nowTime")+1; var text_obj= evt.data +',"time":'+time+'}';//获取加上当前时间 console.log(text_obj); var new_obj=eval('('+text_obj+')'); $('#danmu').danmu("addDanmu",new_obj);//添加弹幕 }; websocket.onerror = function (evt, e) { console.log('Error occured: ' + evt.data); }; //初始化 $("#danmu").danmu({ left:0, top:0, height:"100%", width:"100%", speed:20000, opacity:1, font_size_small:16, font_size_big:24, top_botton_danmu_time:6000 }); //一个定时器,监视弹幕时间并更新到页面上 function timedCount(){ $("#time").text($('#danmu').data("nowTime")); t=setTimeout("timedCount()",50) } timedCount(); function starter(){ $('#danmu').danmu('danmuStart'); } function pauser(){ $('#danmu').danmu('danmuPause'); } function resumer(){ $('#danmu').danmu('danmuResume'); } function stoper(){ $('#danmu').danmu('danmuStop'); } function getime(){ alert($('#danmu').data("nowTime")); } function getpaused(){ alert($('#danmu').data("paused")); } //发送弹幕,使用了文档README.md第7节中推荐的方法 function send(){ var text = document.getElementById('text').value; var color = document.getElementById('color').value; var position = document.getElementById('position').value; //var time = $('#danmu').data("nowTime")+1; var size =document.getElementById('text_size').value; //var text_obj='{ "text":"'+text+'","color":"'+color+'","size":"'+size+'","position":"'+position+'","time":'+time+'}'; //为了处理简单,方便后续加time,和isnew,就先酱紫发一半吧。 //注:time为弹幕出来的时间,isnew为是否加边框,本身发的弹幕,常理上来讲是有边框的。 var text_obj='{ "text":"'+text+'","color":"'+color+'","size":"'+size+'","position":"'+position+'"'; //利用websocket发送 websocket.send(text_obj); //清空相应的内容 document.getElementById('text').value=''; } //调整透明度函数 function op(){ var op=document.getElementById('op').value; $('#danmu').danmu("setOpacity",op/100); } //调隐藏 显示 function changehide() { var op = document.getElementById('op').value; op = op / 100; if (document.getElementById("ishide").checked) { $("#danmu").danmu("setOpacity",1) } else { $("#danmu").danmu("setOpacity",0) } } //设置弹幕时间 function settime(){ var t=document.getElementById("set_time").value; t=parseInt(t) $('#danmu').danmu("setTime",t); } </script> </body> </html>
上述代码须要注意的是websocket的创建和接收,以及send方法中对弹幕的处理。
ws_server.php
<?php //建立websocket服务器对象,监听0.0.0.0:9505端口 $ws = new swoole_websocket_server("0.0.0.0", 9505); //监听WebSocket链接打开事件 $ws->on('open', function ($ws, $request) { //var_dump($request->fd, $request->get, $request->server); //至关于记录一个日志吧,有链接时间和链接ip echo $request->fd.'-----time:'.date("Y-m-d H:i:s",$request->server['request_time']).'--IP--'.$request->server['remote_addr'].'-----'; }); //监听WebSocket消息事件 $ws->on('message', function ($ws, $frame) { //记录收到的消息,能够写到日志文件中 echo "Message: {$frame->data}\n"; //遍历全部链接,循环广播 foreach($ws->connections as $fd){ //若是是某个客户端,本身发的则加上isnew属性,不然不加 if($frame->fd == $fd){ $ws->push($frame->fd, $frame->data.',"isnew":""'); }else{ $ws->push($fd, "{$frame->data}"); } } }); //监听WebSocket链接关闭事件 $ws->on('close', function ($ws, $fd) { echo "client-{$fd} is closed\n"; }); $ws->start();
运行方法:
输入php ws_server.php
先启动服务器端的websocket。若是要后台运行,且不随用户终端关闭而断开,须要建立一个log.txt用于存取上述输出的东西,而后输入nohup php ws_server.php > log.txt &
便可。
而后,
注,若是要用此项目,须要自行修改本身的服务器ip地址。只须要修改var wsServer = 'ws://123.206.61.229:9505';
处便可,后台代码不须要作任何处理。
github地址:https://github.com/diligentyang/danmu
原文博主:http://blog.csdn.net/qq_28602957
如需转载请明示。