<?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); } }