直播

首先 声明一下,直播的前端代码是抄袭自http://www.workerman.net/camera 。本人并无鄙视workman的意思,只是为了告诉某些无脑喷,easySwoole也可以实现直播。

相关代码

更改easySwoole运行模式

修改/Conf/Config.php

  1. "SERVER_TYPE"=>\Core\Swoole\Config::SERVER_TYPE_WEB_SOCKET,//

注册相关事件

在Conf/Event.php的beforeWorkerStart事件中注册相关回调。

  1. $server->on("message",function (\swoole_websocket_server $server, \swoole_websocket_frame $frame){
  2. /*
  3. * 注意 本example未引入redis来做fd信息记录,因此每次采用遍历的形式来获取结果,
  4. * 仅供思路参考,不建议在生产环节使用
  5. */
  6. $list = array();
  7. foreach (Server::getInstance()->getServer()->connections as $connection){
  8. $info = Server::getInstance()->getServer()->connection_info($connection);
  9. if($info['websocket_status']){
  10. $list[] = $connection;
  11. }
  12. }
  13. $data = $frame->data;
  14. AsyncTaskManager::getInstance()->add(function ()use($list,$data){
  15. foreach ( $list as $fd) {
  16. Server::getInstance()->getServer()->push($fd,$data);
  17. }
  18. });
  19. });
  20. $server->on("handshake",function (\swoole_http_request $request, \swoole_http_response $response){
  21. //自定定握手规则,没有设置则用系统内置的(只支持version:13的)
  22. if (!isset($request->header['sec-websocket-key']))
  23. {
  24. //'Bad protocol implementation: it is not RFC6455.'
  25. $response->end();
  26. return false;
  27. }
  28. if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $request->header['sec-websocket-key'])
  29. || 16 !== strlen(base64_decode($request->header['sec-websocket-key']))
  30. )
  31. {
  32. //Header Sec-WebSocket-Key is illegal;
  33. $response->end();
  34. return false;
  35. }
  36. $key = base64_encode(sha1($request->header['sec-websocket-key']
  37. . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
  38. true));
  39. $headers = array(
  40. 'Upgrade' => 'websocket',
  41. 'Connection' => 'Upgrade',
  42. 'Sec-WebSocket-Accept' => $key,
  43. 'Sec-WebSocket-Version' => '13',
  44. 'KeepAlive' => 'off',
  45. );
  46. foreach ($headers as $key => $val)
  47. {
  48. $response->header($key, $val);
  49. }
  50. $response->status(101);
  51. $response->end();
  52. // Server::getInstance()->getServer()->push($request->fd,"hello world,your fd is ".$request->fd);
  53. });

前端界面

/App/Static/camera.html

  1. <html>
  2. <head>
  3. <meta http-equiv="content-type" content="text/html; charset=utf-8">
  4. <title>live cam 录像页面</title>
  5. </head>
  6. <body>
  7. <video autoplay id="sourcevid" style="width:320;height:240px"></video>
  8. <br>
  9. 提示:最好用火狐测试,谷歌浏览器升级了安全策略,谷歌浏览器只能在https下才能利用html5打开摄像头。
  10. <canvas id="output" style="display:none"></canvas>
  11. <script type="text/javascript" charset="utf-8">
  12. var socket = new WebSocket("ws://"+document.domain+":9501");
  13. var back = document.getElementById('output');
  14. var backcontext = back.getContext('2d');
  15. var video = document.getElementsByTagName('video')[0];
  16. var success = function(stream){
  17. video.src = window.URL.createObjectURL(stream);
  18. }
  19. socket.onopen = function(){
  20. draw();
  21. }
  22. var draw = function(){
  23. try{
  24. backcontext.drawImage(video,0,0, back.width, back.height);
  25. }catch(e){
  26. if (e.name == "NS_ERROR_NOT_AVAILABLE") {
  27. return setTimeout(draw, 100);
  28. } else {
  29. throw e;
  30. }
  31. }
  32. if(video.src){
  33. socket.send(back.toDataURL("image/jpeg", 0.5));
  34. }
  35. setTimeout(draw, 100);
  36. }
  37. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
  38. navigator.mozGetUserMedia || navigator.msGetUserMedia;
  39. navigator.getUserMedia({video:true, audio:false}, success, console.log);
  40. </script>
  41. </body>
  42. </html>

/App/Static/index.html

  1. <html>
  2. <head>
  3. <meta http-equiv="content-type" content="text/html; charset=utf-8">
  4. <title>live cam 接收页面</title>
  5. </head>
  6. <body>
  7. <img id="receiver" style="width:320px;height:240px"/>
  8. <br><br>如果显示空白,说明当前没有人在直播,<a href="/camera.html" target="_blank">点击这里直播</a>
  9. <script type="text/javascript" charset="utf-8">
  10. var receiver_socket = new WebSocket("ws://"+document.domain+":9501");
  11. var image = document.getElementById('receiver');
  12. receiver_socket.onmessage = function(data)
  13. {
  14. image.src=data.data;
  15. }
  16. </script>
  17. </body>
  18. </html>

新建控制器

  1. namespace App\Controller;
  2. use Core\AbstractInterface\AbstractController;
  3. use Core\Http\Message\Status;
  4. class Index extends AbstractController
  5. {
  6. function index()
  7. {
  8. // TODO: Implement index() method.
  9. $content = file_get_contents(ROOT."/App/Static/index.html");
  10. $this->response()->write($content);
  11. }
  12. function onRequest($actionName)
  13. {
  14. // TODO: Implement onRequest() method.
  15. }
  16. function actionNotFound($actionName = null, $arguments = null)
  17. {
  18. // TODO: Implement actionNotFound() method.
  19. $this->response()->withStatus(Status::CODE_NOT_FOUND);
  20. }
  21. function afterAction()
  22. {
  23. // TODO: Implement afterAction() method.
  24. }
  25. function camera(){
  26. $content = file_get_contents(ROOT."/App/Static/camera.html");
  27. $this->response()->write($content);
  28. }
  29. }

代码准备完毕

执行 php server start 启动easySwoole

example

注意:本代码仅为实例代码,展示了基础原理。未做房间号、缓存等处理,请勿用于生产环境