小直播后端开发笔记

1,335 阅读2分钟

使用到的一些技术与云服务功能:

调试工具:

  • OBS,用于推流测试,官网
  • VLC,用户拉流测试,官网

点播/直播 协议介绍

HLS(M3U8) 可用于直播 http://xxx.liveplay.myqcloud.com/xxx.m3u8 支持 支持
视频协议 用途 URL 地址格式 PC 浏览器 移动浏览器
HLS(M3U8) 可用于点播 http://xxx.vod.myqcloud.com/xxx.m3u8 支持 支持
FLV 可用于直播 http://xxx.liveplay.myqcloud.com/xxx.flv 支持 不支持
FLV 可用于点播 http://xxx.vod.myqcloud.com/xxx.flv 支持 不支持
RTMP 只适用直播 rtmp://xxx.liveplay.myqcloud.com/live/xxx 支持 不支持
MP4 只适用点播 http://xxx.vod.myqcloud.com/xxx.mp4 支持 支持
  • 移动设备直播,建议使用RTMP 协议,性能较好,网络状态正常下,直播延迟大概 2 ~ 5 秒,移动设备可以正常使用

  • HLS 协议兼容性好,可以被使用与网页直播,但性能缺佳,测试有延迟5秒以上。

腾讯云小直播域名配置

需要配置:

  • 推流域名
  • 拉流域名

配置CNAME解析腾讯云给出的链接即可。

Xnip2020-03-08_17-05-02.jpg

生成推流地址

推流地址通常是给与主播端使用,将视频流数据推向服务端。

使用到了腾讯云小直播服务,根据官方给的例子使用即可。

推流地址生成公式:

rtmp://domain/live/StreamName?txSecret=Md5(key+StreamName+hex(time))&txTime=hex(time)

需要四个参数生成:

  1. 云直播key,腾讯云给的
  2. streamName,区别不同推流地址的唯一流名称,必须唯一,后端处理这个比较方便(user_id+now().timestamp)貌似就可以生成。
  3. 推流域名,比如:69045.livepush.myqcloud.com
  4. 结束时间,比如最长直播持续一天,now().addDay().toDateTimeString();

PHP例子:

/**
      * 获取推流地址
      * 如果不传key和过期时间,将返回不含防盗链的url
      * @param domain 您用来推流的域名
      *        streamName 您用来区别不同推流地址的唯一流名称
      *        key 安全密钥
      *        time 过期时间 sample 2016-11-12 12:00:00
      * @return String url
			*/
			function getPushUrl($domain, $streamName, $key = null, $time = null){
			      if($key && $time){
			            $txTime = strtoupper(base_convert(strtotime($time),10,16));
			            //txSecret = MD5( KEY + streamName + txTime )
			            $txSecret = md5($key.$streamName.$txTime);
			            $ext_str = "?".http_build_query(array(
			                  "txSecret"=> $txSecret,
			                  "txTime"=> $txTime
			            ));
			      }
			      return "rtmp://".$domain."/live/".$streamName . (isset($ext_str) ? $ext_str : "");
			}
			
			echo getPushUrl("123.test.com","123456","69e0daf7234b01f257a7adb9f807ae9f","2016-09-11 20:08:07");

Java 例子:

package com.test;
			
			import java.io.UnsupportedEncodingException;
			import java.security.MessageDigest;
			import java.security.NoSuchAlgorithmException;
			
			public class Test {
			
			      public static void main(String[] args) {
			            System.out.println(getSafeUrl("txrtmp", "11212122", 1469762325L));
			      }
			
			      private static final char[] DIGITS_LOWER =
			            {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
			
			      /*
			      * KEY+ streamName + txTime
			      */
			      private static String getSafeUrl(String key, String streamName, long txTime) {
			            String input = new StringBuilder().
			                              append(key).
			                              append(streamName).
			                              append(Long.toHexString(txTime).toUpperCase()).toString();
			
			            String txSecret = null;
			            try {
			                  MessageDigest messageDigest = MessageDigest.getInstance("MD5");
			                  txSecret  = byteArrayToHexString(
			                              messageDigest.digest(input.getBytes("UTF-8")));
			            } catch (NoSuchAlgorithmException e) {
			                  e.printStackTrace();
			            } catch (UnsupportedEncodingException e) {
			                  e.printStackTrace();
			            }
			
			            return txSecret == null ? "" :
			                              new StringBuilder().
			                              append("txSecret=").
			                              append(txSecret).
			                              append("&").
			                              append("txTime=").
			                              append(Long.toHexString(txTime).toUpperCase()).
			                              toString();
			      }
			
			      private static String byteArrayToHexString(byte[] data) {
			            char[] out = new char[data.length << 1];
			
			            for (int i = 0, j = 0; i < data.length; i++) {
			                  out[j++] = DIGITS_LOWER[(0xF0 & data[i]) >>> 4];
			                  out[j++] = DIGITS_LOWER[0x0F & data[i]];
			            }
			            return new String(out);
			      }
			}

使用OBS调试推流功能

image.png

  • 服务器填写腾讯上的推流地址。

  • 串流密钥填写上面例子生成的地址中的 live/后的字符串即可。

串流密钥例子:

test?txSecret=592172b126b77b71dbda74b1edf9b5de&txTime=5E65167F

测试推流是否成功

打开VLC,使用快捷键 Command+n,输入推流域名+流名称即可。

例子:

https://xxx.com/live/test

如果成功,可以双击地址直接打开,可以看到你的推流画面

弹幕功能

服务端将弹幕相关数据,广播到当前直播间,前端使用 基于Laravel-echo包去监听数据,再讲数据渲染到当前直播间即可。

PHP代码例子:

public function commentLiveRoomResolver($root, array $args, $context, $info)
    {
        $user         = getUser();
        $live_room_id = Arr::get($args, 'live_room_id', null);
        $message      = Arr::get($args, 'message', null);

        event(new NewLiveRoomMessage($user->id, $live_room_id, $message));
        return $message;
    }

NewLiveRoomMessage代码:

<?php

namespace App\Events\LiveRoom;

use App\LiveRoom;
use App\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class NewLiveRoomMessage
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $user;
    public $message;
    public $liveRoom;

    /**
     * Create a new event instance.
     *
     * @param $userId 观众id
     * @param $liveRoomId 直播室id
     * @param $message 弹幕内容
     */
    public function __construct($userId,$liveRoomId,$message)
    {
        $this->user = User::find($userId);
        $this->liveRoom = LiveRoom::find($liveRoomId);
        $this->message = $message;
    }

    public function broadcastWith():array
    {
        return [
            'user_id' => $this->user->id,
            'user_name' => $this->user->name,
            'user_avatar' => $this->user->avatar_url,
            'live_room_id' => $this->liveRoom->id,
            'message' => $this->message,
        ];
    }

    public function broadcastOn(): PresenceChannel
    {
        return new PresenceChannel('live_room.'.$this->liveRoom->id);
    }
}

如果需要记录直播间弹幕数据到数据库中,再去添加NewLiveRoomMessage的监听器中相关代码即可。