LiveSDK.class.php 15.3 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
<?php
/**
 * 腾讯云直播 SDK
 * Created by PhpStorm.
 * User: zhoutao
 * Date: 2018/1/9
 * Time: 下午4:32
 */

namespace Com;

use Org\Net\Snoopy;

class LiveSDK extends Live
{
    /**
     * 直播开关状态 0: 表示禁用 1: 表示允许推流 2: 表示断流
     */
    const CHANNEL_STATUS_DISABLED = 0;
    const CHANNEL_STATUS_ALLOW = 1;
    const CHANNEL_STATUS_CUT_OFF = 2;
    const CHANNEL_STATUS_LIST = [
        self::CHANNEL_STATUS_DISABLED,
        self::CHANNEL_STATUS_ALLOW,
        self::CHANNEL_STATUS_CUT_OFF
    ];

    /**
     * 直播间管理动作 断流:forbid;恢复推流:resume
     */
    const CHANNEL_MANAGER_FORBID = 'forbid';
    const CHANNEL_MANAGER_RESUME = 'resume';

    /**
     * 直播间开启录制任务 0: 非实时 1: 实时
     */
    const TAPE_TASK_SUB_TYPE_NOT_REAL_TIME = 0;
    const TAPE_TASK_SUB_TYPE_REAL_TIME = 1;

    /**
     * 开启关闭推流
     * @param string $channelId 直播码
     * @param int $status 开关状态 0 表示禁用,1 表示允许推流,2 表示断流
     * @return bool
     */
    public function channelSetStatus($channelId, $status)
    {
        if (!in_array($status, self::CHANNEL_STATUS_LIST) || empty($channelId)) {
            return false;
        }

        return $this->requestSdk('Live_Channel_SetStatus', 'fcgi', [
            'Param.s.channel_id' => $channelId,
            'Param.n.status' => $status
        ]);
    }

    /**
     * 查询频道列表
     * @param null   $status 0:表示断流,1:表示开启,3:表示关闭 默认是不过滤
     * @param int    $page 分页页码 从 1 开始,默认为 1
     * @param int    $limit 分页大小 10~100,默认为 10
     * @param string $orderField 排序字段 可选字段:create_time,默认为create_time
     * @param null   $orderType 排序方法 0:表示正序,1:表示倒序
     * @return bool
     */
    public function channelGetChannelList($status = null, $page = 1, $limit = 10, $orderField = '', $orderType = null)
    {
        // 如果有值则对应上字段传输
        $params = [];
        $field = [
            'Param.n.status',
            'Param.n.page_no',
            'Param.n.page_size',
            'Param.s.order_field',
            'Param.n.order_type'
        ];
        foreach (func_get_args() as $key => $item) {
            if (!empty($item)) {
                $params[$field[$key]] = $item;
            }
        };


        return $this->requestSdk('Live_Channel_GetChannelList', 'fcgi', $params);
    }

    /**
     * 暂停推流并延迟恢复接口
     * @param string $channelId 直播码
     * @param int $abstimeEnd 禁播截止的时间戳  禁播截止的绝对时间,请填写UNIX 时间戳(十进制),系统最多禁播三个月。
     * @param string $action 动作  断流:forbid;恢复推流:resume
     * @return bool
     */
    public function channelManager($channelId, $abstimeEnd, $action)
    {
        return $this->requestSdk('channel_manager', 'fcgi', [
            'Param.s.channel_id' => $channelId,
            'Param.n.abstime_end' => $abstimeEnd,
            'Param.s.action' => $action
        ]);
    }

    /**
     * 创建录制任务
     * @param String $channelId 频道 ID
     * @param String $startTime 任务开始时间 标准的 date_time,需要 urlencode,如 2017-01-01%2010:10:01
     * @param String $endTime 任务结束时间 标准的 date_time,需要 urlencode,如 2017-01-01%2010:10:01。
     * @param Int $taskSubType 是否开启实时视频录制 默认 0,1 表示开启实时视频录制。
        (1)若开启实时视频录制,调用接口则同步开始录制,此时如果传入任务开始时间参数,任务开始时间参数无效。
        (2)开启实时视频录制的同时如果传入了任务结束时间,则按照任务结束时间结束录制。若没传入,则 30 分钟后自动结束录制。
        (3)实时录制任务开始时间与任务结束时间超过 30 分钟,则 30 分钟后会自动结束录制,实时视频录制建议控制台在 5 分钟以内。
     * @param String $fileFormat 录制文件格式 默认 flv;可取值 flv、hls、mp4、aac
     * @param String $recordType 录制文件类型 默认 video
        当 record_type 取值“video”时,file_format 可以取值 “flv”,"hls", "mp4"
        当 record_type 取值“audio”时,file_format 可以取值 “aac”,“flv”,“hls”,“mp4”
     * @return bool
     */
    public function liveTapeStart(
        $channelId, $startTime, $endTime,
        $taskSubType = self::TAPE_TASK_SUB_TYPE_NOT_REAL_TIME, $fileFormat = 'flv', $recordType = 'video')
    {
        return $this->requestSdk('Live_Tape_Start', 'fcgi', [
            'Param.s.channel_id' => $channelId,
            'Param.s.start_time' => $startTime,
            'Param.s.end_time' => $endTime,
            'Param.n.task_sub_type' => $taskSubType,
            'Param.s.file_format' => $fileFormat,
            'Param.s.record_type' => $recordType
        ]);
    }

    /**
     * 结束录制任务
     * @param String $channelId 频道 ID
     * @param String $taskId 任务 ID
     * @param Int $taskSubType 是否开启实时视频录制 默认 0,1 表示开启小视频录制
     * @return bool
     */
    public function liveTapeStop($channelId, $taskId, $taskSubType = self::TAPE_TASK_SUB_TYPE_NOT_REAL_TIME)
    {
        return $this->requestSdk('Live_Tape_Stop', 'fcgi', [
            'Param.s.channel_id' => $channelId,
            'Param.s.task_id' => $taskId,
            'Param.n.task_sub_type' => $taskSubType
        ]);
    }

    /**
     * 查询录制文件
     * @param String $channelId 直播码
     * @param String $startTime 查询开始时间 格式为:2016-12-10 00:00:00
     * @param String $endTime 查询结束时间 格式为:2016-12-10 00:00:00。结束时间距开始时间一天以内,且不能跨天
     * @param Int $page 分页页码 从 1 开始,默认为 1
     * @param Int $limit 分页大小 1~100,默认为 10
     * @param String $orderType 排序方式 asc 表示升序,desc 表示降序,默认 asc
     * @return bool
     */
    public function liveTapeGetFilelist(
        $channelId, $startTime = null, $endTime = null, $page = 1, $limit = 10, $orderType = 'asc')
    {
        // 如果有值则对应上字段传输
        $params = [];
        $field = [
            'Param.s.channel_id',
            'Param.s.start_time',
            'Param.s.end_time',
            'Param.n.page_no',
            'Param.n.page_size',
            'Param.s.sort_type',
        ];
        foreach (func_get_args() as $key => $item) {
            if (!empty($item)) {
                $params[$field[$key]] = $item;
            }
        };

        return $this->requestSdk('Live_Tape_GetFilelist', 'fcgi', $params);
    }

    /**
     * 获取播放统计历史信息
     * @param int $startTime 必填 查询起始时间 时间戳 15 天内的数据
     * @param int $endTIme 必填 结束时间 时间戳建议时间跨度不大于 2 小时
     * @param string $streamId 非必填 流 ID 不填就是获取总带宽
     * @param string $domain 非必填 域名 若不填,取这个 APPID 下的总数据,需要填写 cname 前的原始播放域名
     * @return mixed
     */
    public function getLivePlayStatHistory($startTime, $endTIme, $streamId = '', $domain = '')
    {
        // 如果有值则对应上字段传输
        $params = [];
        $field = [
            'Param.n.start_time',
            'Param.n.end_time',
            'Param.s.stream_id',
            'Param.s.domain'
        ];
        foreach (func_get_args() as $key => $item) {
            if (!empty($item)) {
                $params[$field[$key]] = $item;
            }
        };

        return $this->requestSdk('Get_LivePlayStatHistory', 'statcgi', $params);
    }

    /**
     * 查询指定直播流的推流和播放信息
     * @param string $streamId 直播码 如不设置 stream_id:查询所有正在直播中的流
     * @param int    $page 分页页码 从 1 开始,默认为 1
     * @param int    $pageSize 分页大小 1~300,默认为 300
     * @param string $pullDomain 拉流域名 即播放域名,如果不填则返回所有域名的播放数据
     * @return bool
     */
    public function getLiveStat($streamId, $page = 1, $pageSize = 300, $pullDomain = '')
    {
        // 如果有值则对应上字段传输
        $params = [];
        $field = [
            'Param.s.stream_id',
            'Param.n.page_no',
            'Param.n.page_size',
            'Param.s.pull_domain'
        ];
        foreach (func_get_args() as $key => $item) {
            if (!empty($item)) {
                $params[$field[$key]] = $item;
            }
        };

        return $this->requestSdk('Get_LiveStat', 'statcgi', $params);
    }

    /**
     * 同 getLiveStat 方法 只是 仅返回播放统计信息以提高查询效率
     * @param        $streamId
     * @param int    $page
     * @param int    $pageSize
     * @param string $pullDomain
     * @return bool
     */
    public function getLivePlayStat($streamId, $page = 1, $pageSize = 300, $pullDomain = '')
    {
        // 如果有值则对应上字段传输
        $params = [];
        $field = [
            'Param.s.stream_id',
            'Param.n.page_no',
            'Param.n.page_size',
            'Param.s.pull_domain'
        ];
        foreach (func_get_args() as $key => $item) {
            if (!empty($item)) {
                $params[$field[$key]] = $item;
            }
        };

        return $this->requestSdk('Get_LivePlayStat', 'statcgi', $params);
    }
}

abstract class Live
{
    /**
     * 操作类
     */
    const TYPE_OF_OPERATION = 'fcgi';

    /**
     * 统计类
     */
    const TYPE_OF_STAT = 'statcgi';

    /**
     * 基础 URL
     */
    const BASE_URL = 'http://%s.video.qcloud.com/common_access?%s';

    /**
     * API鉴权key
     * @var string
     */
    private $apiAuthKey = '';

    /**
     * 推流防盗链Key
     * @var string
     */
    private $antiTheftChain = '';

    /**
     * AppId
     * @var int
     */
    private $appId = null;

    /**
     * BizId
     * @var int
     */
    private $bizId = null;

    public function __construct()
    {
        $this->apiAuthKey = cfg('TENCENT_LIVE_API_AUTH_KEY');
        $this->antiTheftChain = cfg('TENCENT_LIVE_ANTI_THEFT_CHAIN_KEY');
        $this->appId = cfg('TENCENT_LIVE_APPID');
        $this->bizId = cfg('TENCENT_LIVE_BIZID');
    }

    /**
     * 请求腾讯云 SDK
     * @param        $interface
     * @param        $type
     * @param array  $urlParams
     * @param array  $postParams
     * @param array  $headers
     * @param string $method
     * @param null   $file
     * @return bool
     */
    protected function requestSdk(
        $interface, $type, $urlParams = [], $postParams = [], $headers = [], $method = 'GET', $file = null
    ) {
        // 防止太快过期 + 10秒
        $t = NOW_TIME + 10;
        $baseParams = [
            'appid' => $this->appId,
            'interface' => $interface,
            't' => $t,
            'sign' => md5($this->apiAuthKey . $t),
        ];
        $params = [
            self::BASE_URL,
            $type,
            http_build_query(array_merge($baseParams, $urlParams)),
        ];
        $url = call_user_func_array('sprintf', $params);

        \Think\Log::record('腾讯云直播 接口 ::: ' . $interface . ' 请求参数 ::: ' . var_export($postParams, true));
        return $this->request($url, $postParams, $headers, $method, $file);
    }

    /**
     * 请求接口数据
     * @param string       $url 请求URL
     * @param string|array $reqParams 请求数据
     * @param array        $headers 请求头部
     * @param string       $method 请求方式, 如: GET/POST/DELETE/PUT
     * @param mixed        $files 文件路径
     * @param bool         $retry 是否重试
     * @return bool
     */
    protected function request($url, $reqParams, $headers = [], $method = 'GET', $files = null, $retry = false)
    {
        $snoopy = new Snoopy();

        // 使用自定义的头字段,格式为 array(字段名 => 值, ... ...)
        $snoopy->rawheaders = $headers;
        // 非 GET 协议, 需要设置
        $method = rstrtoupper($method);
        $methods = array('GET', 'POST', 'PUT', 'DELETE');
        if (!in_array($method, $methods)) {
            $method = 'GET';
        }

        // 设置协议
        if (!empty($files)) {
            // 如果需要传文件
            $method = 'POST';
            $snoopy->set_submit_multipart();
        } else {
            $snoopy->set_submit_normal('application/json');
        }

        // 判断协议
        $snoopy->set_submit_method($method);
        switch (rstrtoupper($method)) {
            case 'POST' :
            case 'PUT' :
            case 'DELETE' :
                $result = $snoopy->submit($url, $reqParams, $files);
                break;
            default :
                $result = $snoopy->fetch($url);
                break;
        }

        // 如果读取错误
        if (!$result || 200 != $snoopy->status) {
            \Think\Log::record('请求错误::' . var_export($snoopy, true));
        }

        // 获取返回数据 (腾讯云返回的结果不是纯的 json 字符串)
        preg_match("/\{.*\}/is", $snoopy->results, $matches);
        $result = json_decode($matches[0], true);
        \Think\Log::record('返回数据:::' . var_export($matches[0], true));

        return $result;
    }

    /**
     * 获取推流地址
     * 如果不传key和过期时间,将返回不含防盗链的url
     * @param String $livecode 直播码 bizid+"_"+stream_id  如 8888_test123456
     * @param String $time 过期时间 10位数时间戳
     * @return array 0 推流地址 1 推流名称
     */
    public function getPushUrl($livecode, $time)
    {
        $txTime = strtoupper(base_convert($time, 10, 16));

        $ext_str = '?' . http_build_query(array(
                'bizid' => $this->bizId,
                // MD5( KEY + livecode + txTime )
                'txSecret' => md5($this->antiTheftChain . $livecode . $txTime),
                'txTime' => $txTime,
            ));

        return [
            'rtmp://' . $this->bizId . '.livepush.myqcloud.com/live/',
            $livecode . (isset($ext_str) ? $ext_str : '')
        ];
    }

    /**
     * 获取播放地址
     * @param String bizId 您在腾讯云分配到的bizid
     * @param String $livecode 直播码 bizid+"_"+stream_id  如 8888_test123456
     * @return String url
     */
    public function getPlayUrl($livecode)
    {
        return array(
            'rtmp://' . $this->bizId . '.liveplay.myqcloud.com/live/' . $livecode,
            'http://' . $this->bizId . '.liveplay.myqcloud.com/live/' . $livecode . '.flv',
            'http://' . $this->bizId . '.liveplay.myqcloud.com/live/' . $livecode . '.m3u8',
        );
    }

    /**
     * 获取直播码 腾讯云回调通知内容内的 stream_id 或者说 channel_id 两个是一样的 腾讯云自己的历史原因
     * @param $id
     * @return string
     */
    public function getLiveCode()
    {
        return $this->bizId . '_' . $this->getStreamId();
    }

    /**
     * 获取直播标识
     * @return string
     */
    private function getStreamId()
    {
        // 单个企业只会有一个
        return strtolower(QY_DOMAIN);
    }
}