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