LiveMsgController.class.php 7.12 KB
<?php
/**
 * 接收腾讯云直播
 * WIKI: https://cloud.tencent.com/document/product/267/5957
 * Created by PhpStorm.
 * User: zhoutao
 * Date: 2018/1/10
 * Time: 下午5:58
 */

namespace Apicp\Controller\Callback;

use Com\LiveSDK;
use Common\Common\Constant;
use Common\Model\StudioModel;
use Common\Service\MainService;
use Common\Service\StudioFileService;
use Common\Service\StudioLogService;
use Common\Service\StudioService;
use Think\Controller\RestController;

class LiveMsgController extends RestController
{
    /**
     * 消息事件类型
     * 0: 断流
     * 1: 推流
     * 100: 新的录制文件已生成
     * 200: 新的截图文件已生成
     */
    const EVENT_TYPE_CUT_OFF = 0;
    const EVENT_TYPE_PUSH_FLOW = 1;
    const EVENT_TYPE_DOCUMENTS_LANDING = 100;

    /**
     * 通知内容
     * @var array
     */
    private $streamData = [];

    public function index()
    {
        // 获取消息数据并且验证签名
        $this->getMsgParamsAndValidateSign();

        try {
            // 查询直播表数据
            $studioServ = new StudioService();
            $studioServ->start_trans();
            // 因为直播码会重复 所以获取最新的一条
            $studioDetail = $studioServ->get_by_conds(
                ['stream_id' => $this->streamData['stream_id']],
                ['created' => 'DESC'],
                true
            );
            // 获取直播活动数据
            $mainServ = new MainService();
            $mainDetail = $mainServ->get($studioDetail['lm_id'], true);
            \Think\Log::record('直播间 ::: ' . var_export($studioDetail, true) .
                '直播活动 ::: ' . var_export($mainDetail, true));

            // 消息事件处理
            $liveSdk = new LiveSDK();
            switch ($this->streamData['event_type']) {
                // 断流
                case self::EVENT_TYPE_CUT_OFF:

                    break;
                // 推流
                case self::EVENT_TYPE_PUSH_FLOW:
                    // 如果没有 直播活动 或者 直播流 或者 直播流现在应该是被禁用的
                    if (empty($studioDetail || empty($mainDetail)) ||
                            ($studioDetail['stream_status'] == Constant::STREAM_TYPE_STATUS_DISABLED)) {
                        // 关闭推流地址
                        $liveSdk->channelSetStatus($this->streamData['stream_id'], $liveSdk::CHANNEL_STATUS_CUT_OFF);
                        break;
                    }

                    // 还没到开始推流时间
                    $beforeAllowStartTime =
                        MILLI_TIME <= ($mainDetail['start_time'] - Constant::LIVE_ALLOW_EARLY_START_TIME);
                    // 结束时间
                    $endTime = $mainDetail['start_time'] + Constant::LIVE_TOTAL_PLAYING_TIME * 60 * 60 * 1000;

                    // 暂缓到设置的开始时间前往推一小时 才能推流
                    if ($beforeAllowStartTime) {
                        $allowStartTime = ($mainDetail['start_time'] - Constant::LIVE_ALLOW_EARLY_START_TIME) / 1000;
                        $liveSdk->channelManager(
                            $this->streamData['stream_id'],
                            $allowStartTime,
                            $liveSdk::CHANNEL_MANAGER_FORBID
                        );

                    // 超过时间还推流 (腾讯那边 似乎已经根据超时时间做了禁推, 会推个断流类型的通知过来)
                    } elseif ($endTime <= MILLI_TIME) {
                        // 关闭推流地址
                        $liveSdk->channelSetStatus($this->streamData['stream_id'], $liveSdk::CHANNEL_STATUS_DISABLED);
                        // 更新直播室状态
                        $studioServ->update(
                            $studioDetail['ls_id'],
                            ['stream_status' => $studioServ::STREAM_STATUS_DISABLED],
                            true
                        );
                    }

                    break;
                // 新的文件落地
                case self::EVENT_TYPE_DOCUMENTS_LANDING:
                    // 记录推流时长
                    $mainServ->update(
                        $mainDetail['lm_id'],
                        ['push_total_time = push_total_time + ?' => $this->streamData['duration']],
                        true
                    );

                    // 记录文件表
                    $studioFileServ = new StudioFileService();
                    $studioFileInsertData = array_intersect_key(
                        $this->streamData,
                        array_fill_keys([
                            'task_id',
                            'video_id',
                            'video_url',
                            'file_format',
                            'file_id',
                            'file_size',
                            'record_file_id',
                    ], ''));
                    $studioFileInsertData['lm_id'] = $mainDetail['lm_id'];
                    $studioFileInsertData['domain'] = $mainDetail['domain'];
                    $studioFileServ->insert($studioFileInsertData);

                    break;
            }

            $studioServ->commit();
        } catch (\Exception $e) {
            \Think\Log::record('处理消息通知出错:::' . var_export($e, true));
            $studioServ->rollback();
        }

        // 记录消息
        try {
            $this->insertStudioLog($mainDetail);
        } catch (\Exception $e) {
            \Think\Log::record('记录消息出错::: {' . $e->getMessage()  . '} ' . var_export($this->streamData, true));
        }

        $this->end();
    }

    /**
     * 获取消息数据并且验证签名
     */
    private function getMsgParamsAndValidateSign()
    {
        // 接收数据流
        $streamData = file_get_contents("php://input");
        // 将Json转为array
        $streamData = json_decode($streamData, true);

        // 验证签名 或者 超过有效时间
        $signSuccess = isset($streamData['sign']) &&
                md5(cfg('TENCENT_LIVE_API_AUTH_KEY') . $streamData['t']) == $streamData['sign'];
        if (!$signSuccess || NOW_TIME > $streamData['t']) {
            $this->end();
        }

        $this->streamData = $streamData;
        return true;
    }

    /**
     * 回复通知
     */
    private function end()
    {
        parent::_response(['code' => 0], 'json');
    }

    /**
     * 写入消息回调记录
     * @param $mainDetail
     * @return bool
     */
    private function insertStudioLog($mainDetail)
    {
        $studioLogInsertData = array_intersect_key(
            $this->streamData,
            array_fill_keys([
                'errcode',
                'errmsg',
                'event_type',
                'sequence',
                'stream_id',
            ], ''));
        $studioLogInsertData['params_json'] = json_encode($this->streamData);
        $studioLogInsertData['domain'] = isset($mainDetail['domain']) ? $mainDetail['domain'] : QY_DOMAIN;
        $studioLogServ = new StudioLogService();
        $studioLogServ->insert($studioLogInsertData);

        return true;
    }
}