dever 6 years ago
commit
e7ec5da9a6

+ 12 - 0
config/base.php

@@ -0,0 +1,12 @@
+<?php
+$config['base'] = array
+(
+	# 功能类型
+	'live_type' => array
+	(
+		1 => '七牛云',
+	),
+
+	'live_key' => 'signature_dever_2018',
+);
+return $config;

+ 137 - 0
database/active.php

@@ -0,0 +1,137 @@
+<?php
+$live = function()
+{
+    $array = array();
+    $info = Dever::db('live/info')->state();
+    if($info)
+    {
+        $array += $info;
+    }
+    return $array;
+};
+$type = Dever::config('base', 'live')->live_type;
+
+$status = Dever::config('base')->status;
+
+return array
+(
+    # 表名
+    'name' => 'active',
+    # 显示给用户看的名称
+    'lang' => '直播活动',
+    'order' => 20,
+    # 数据结构
+    'struct' => array
+    (
+    
+        'id'        => array
+        (
+            'type'      => 'int-11',
+            'name'      => 'ID',
+            'default'   => '',
+            'desc'      => '',
+            'match'     => 'is_numeric',
+            'search'    => 'order',
+            //'list'        => true,
+        ),
+
+        'name'      => array
+        (
+            'type'      => 'varchar-300',
+            'name'      => '活动名称',
+            'default'   => '',
+            'desc'      => '活动名称',
+            'match'     => 'option',
+            'update'    => 'text',
+            'list'      => true,
+        ),
+
+        'live_id'       => array
+        (
+            'type'      => 'int-11',
+            'name'      => '所属直播云',
+            'default'   => '1',
+            'desc'      => '所属直播云',
+            'match'     => 'is_numeric',
+            'update'    => 'select',
+            'option'    => $live,
+            'search'    => 'select',
+            'list'      => true,
+        ),
+
+        'status'        => array
+        (
+            'type'      => 'int-11',
+            'name'      => '上线状态',
+            'default'   => '2',
+            'desc'      => '上线状态',
+            'match'     => 'is_numeric',
+            //'update'  => 'select',
+            'option'    => $status,
+            'search'    => 'select',
+            'list'      => true,
+            'edit'      => true,
+        ),
+
+        'reorder'		=> array
+		(
+			'type' 		=> 'int-11',
+			'name' 		=> '排序(数值越大越靠前)',
+			'default' 	=> '1',
+			'desc' 		=> '请输入排序',
+			'match' 	=> 'option',
+			'update'	=> 'text',
+			'search'	=> 'order',
+			'list'		=> true,
+			'order'		=> 'desc',
+			'edit'		=> true,
+		),
+
+        'state'     => array
+        (
+            'type'      => 'tinyint-1',
+            'name'      => '状态',
+            'default'   => '1',
+            'desc'      => '请选择状态',
+            'match'     => 'is_numeric',
+        ),
+        
+        'cdate'     => array
+        (
+            'type'      => 'int-11',
+            'name'      => '创建时间',
+            'match'     => array('is_numeric', time()),
+            'desc'      => '',
+            # 只有insert时才生效
+            'insert'    => true,
+            //'search'  => 'date',
+            'list'      => 'date("Y-m-d H:i:s", {cdate})',
+        ),
+    ),
+
+    'manage' => array
+    (
+        //'excel'   => true,
+        //'delete' => false,
+        //'edit' => false,
+        //'insert' => false,
+    ),
+
+    # request 请求接口定义
+    'request' => array
+    (
+        'getAll' => array
+        (
+            # 匹配的正则或函数 选填项
+            'option' => array
+            (
+                'status' => 'yes',
+                'state' => 1,
+            ),
+            'type' => 'all',
+            'order' => array('id' => 'desc'),
+            'col' => 'id,name,live_id',
+        ),
+    )
+
+);

+ 191 - 0
database/info.php

@@ -0,0 +1,191 @@
+<?php
+
+$type = Dever::config('base', 'live')->live_type;
+return array
+(
+    # 表名
+    'name' => 'info',
+    # 显示给用户看的名称
+    'lang' => '直播云配置',
+    'order' => 10,
+    # 数据结构
+    'struct' => array
+    (
+    
+        'id'        => array
+        (
+            'type'      => 'int-11',
+            'name'      => 'ID',
+            'default'   => '',
+            'desc'      => '',
+            'match'     => 'is_numeric',
+            'search'    => 'order',
+            //'list'        => true,
+        ),
+
+        'name'      => array
+        (
+            'type'      => 'varchar-300',
+            'name'      => '配置名称',
+            'default'   => '',
+            'desc'      => '配置名称',
+            'match'     => 'option',
+            'update'    => 'text',
+            'list'      => true,
+        ),
+
+        'type'       => array
+        (
+            'type'      => 'int-11',
+            'name'      => '所属云',
+            'default'   => '1',
+            'desc'      => '所属云',
+            'match'     => 'is_numeric',
+            'update'    => 'select',
+            'option'    => $type,
+            'search'    => 'select',
+        ),
+
+        'host'      => array
+        (
+            'type'      => 'varchar-800',
+            'name'      => '存储空间域名',
+            'default'   => '',
+            'desc'      => '存储空间域名',
+            'match'     => 'is_string',
+            'update'    => 'text',
+            'search'    => 'fulltext',
+            'list'      => true,
+        ),
+
+        'space'      => array
+        (
+            'type'      => 'varchar-300',
+            'name'      => '云空间名-请提前建好云空间,后续会迭代优化,增加云空间的创建和更新,本版本暂时使用已经创建好的云空间',
+            'default'   => '',
+            'desc'      => '空间名',
+            'match'     => 'option',
+            'update'    => 'text',
+            'list'      => true,
+        ),
+
+        'appkey'      => array
+        (
+            'type'      => 'varchar-80',
+            'name'      => 'appkey',
+            'default'   => '',
+            'desc'      => 'appkey',
+            'match'     => 'is_string',
+            'update'    => 'text',
+            'search'    => 'fulltext',
+            'list'      => true,
+        ),
+
+        'appsecret'      => array
+        (
+            'type'      => 'varchar-200',
+            'name'      => 'appsecret',
+            'default'   => '',
+            'desc'      => 'appsecret',
+            'match'     => 'is_string',
+            'update'    => 'text',
+            'search'    => 'fulltext',
+            'list'      => true,
+        ),
+
+        'domain_publish'      => array
+        (
+            'type'      => 'varchar-200',
+            'name'      => '推流域名',
+            'default'   => '',
+            'desc'      => '推流域名',
+            'match'     => 'is_string',
+            'update'    => 'text',
+            //'search'    => 'fulltext',
+            //'list'      => true,
+        ),
+
+        'domain_live_rtmp'      => array
+        (
+            'type'      => 'varchar-200',
+            'name'      => '高清直播域名',
+            'default'   => '',
+            'desc'      => '高清直播域名',
+            'match'     => 'is_string',
+            'update'    => 'text',
+            //'search'    => 'fulltext',
+            //'list'      => true,
+        ),
+
+        'domain_live_hls'      => array
+        (
+            'type'      => 'varchar-200',
+            'name'      => '标清直播域名',
+            'default'   => '',
+            'desc'      => '标清直播域名',
+            'match'     => 'is_string',
+            'update'    => 'text',
+            //'search'    => 'fulltext',
+            //'list'      => true,
+        ),
+
+        'domain_live_hdl'      => array
+        (
+            'type'      => 'varchar-200',
+            'name'      => '普清直播域名',
+            'default'   => '',
+            'desc'      => '普清直播域名',
+            'match'     => 'is_string',
+            'update'    => 'text',
+            //'search'    => 'fulltext',
+            //'list'      => true,
+        ),
+
+        'domain_live_pic'      => array
+        (
+            'type'      => 'varchar-200',
+            'name'      => '直播截图域名',
+            'default'   => '',
+            'desc'      => '直播截图域名',
+            'match'     => 'is_string',
+            'update'    => 'text',
+            //'search'    => 'fulltext',
+            //'list'      => true,
+        ),
+
+        'state'     => array
+        (
+            'type'      => 'tinyint-1',
+            'name'      => '状态',
+            'default'   => '1',
+            'desc'      => '请选择状态',
+            'match'     => 'is_numeric',
+        ),
+        
+        'cdate'     => array
+        (
+            'type'      => 'int-11',
+            'name'      => '创建时间',
+            'match'     => array('is_numeric', time()),
+            'desc'      => '',
+            # 只有insert时才生效
+            'insert'    => true,
+            //'search'  => 'date',
+            'list'      => 'date("Y-m-d H:i:s", {cdate})',
+        ),
+    ),
+
+    'manage' => array
+    (
+        //'excel'   => true,
+        //'delete' => false,
+        //'edit' => false,
+        //'insert' => false,
+    ),
+
+    'request' => array
+    (
+        
+    )
+
+);

+ 283 - 0
database/stream.php

@@ -0,0 +1,283 @@
+<?php
+
+$live = function()
+{
+    $array = array();
+    $info = Dever::db('live/info')->state();
+    if($info)
+    {
+        $array += $info;
+    }
+    return $array;
+};
+
+$active = function()
+{
+    $array = array();
+    $info = Dever::db('live/active')->state();
+    if($info)
+    {
+        $array += $info;
+    }
+    return $array;
+};
+
+$status = array
+(
+    1 => '未开始',
+    2 => '直播中',
+    3 => '已结束',
+);
+
+$disabled = array
+(
+    1 => '启动',
+    2 => '禁播',
+);
+
+return array
+(
+    # 表名
+    'name' => 'stream',
+    # 显示给用户看的名称
+    'lang' => '直播流管理',
+    'order' => 2,
+    'end' => array
+    (
+        //'update' => 'live/lib/manage.createStream',
+        'insert' => 'live/lib/manage.createStream',
+
+        'update_disabled' => 'live/lib/manage.updateDisabled',
+    ),
+    # 数据结构
+    'struct' => array
+    (
+    
+        'id'        => array
+        (
+            'type'      => 'int-11',
+            'name'      => 'ID',
+            'default'   => '',
+            'desc'      => '',
+            'match'     => 'is_numeric',
+            'search'    => 'order',
+            //'list'        => true,
+        ),
+
+        'live_id'       => array
+        (
+            'type'      => 'int-11',
+            'name'      => '所属直播云',
+            'default'   => '1',
+            'desc'      => '所属直播云',
+            'match'     => 'is_numeric',
+            'update'    => 'select',
+            'option'    => $live,
+            'search'    => 'select',
+            //'list'      => true,
+        ),
+
+        'active_id'       => array
+        (
+            'type'      => 'int-11',
+            'name'      => '所属活动',
+            'default'   => '1',
+            'desc'      => '所属活动',
+            'match'     => 'is_numeric',
+            'update'    => 'select',
+            'option'    => $active,
+            'search'    => 'select',
+            'list'      => true,
+        ),
+
+        'name'      => array
+        (
+            'type'      => 'varchar-300',
+            'name'      => '流名称',
+            'default'   => '',
+            'desc'      => '名称',
+            'match'     => 'option',
+            'update'    => 'text',
+            'search'    => 'fulltext',
+            'list_name' => '流信息',
+            'list'      => 'Dever::load("live/lib/manage.liveData", {id})',
+        ),
+
+        'key'      => array
+        (
+            'type'      => 'varchar-300',
+            'name'      => '流标识-流标识在直播空间中唯一,可包含 字母、数字、中划线、下划线;1 ~ 200 个字符长,对应云上的流名称。
+',
+            'default'   => '',
+            'desc'      => '流标识',
+            'match'     => 'option',
+            'search'    => 'fulltext',
+            'update'    => 'text',
+            //'list'      => true,
+        ),
+
+        'times'      => array
+        (
+            'type'      => 'varchar-30',
+            'name'      => '直播时长-直接输入小时数,默认为5个小时',
+            'default'   => '5',
+            'desc'      => '直播时长',
+            'match'     => 'option',
+            'update'    => 'text',
+            //'list'      => true,
+        ),
+
+        'status'       => array
+        (
+            'type'      => 'int-11',
+            'name'      => '流状态',
+            'default'   => '1',
+            'desc'      => '流状态',
+            'match'     => 'is_numeric',
+            //'update'    => 'select',
+            'option'    => $status,
+            'search'    => 'select',
+            //'list'      => true,
+        ),
+
+        'live'      => array
+        (
+            'type'      => 'varchar-800',
+            'name'      => '推流地址',
+            'default'   => '',
+            'desc'      => '推流地址',
+            'match'     => 'option',
+            //'update'    => 'text',
+            'list_name'      => '直播地址',
+            'list'      => 'Dever::load("live/lib/manage.live", {id})',
+        ),
+
+        'url_rtmp'      => array
+        (
+            'type'      => 'varchar-800',
+            'name'      => '高清地址',
+            'default'   => '',
+            'desc'      => '直播地址',
+            'match'     => 'option',
+            //'update'    => 'text',
+            //'list'      => true,
+        ),
+
+        'url_hls'      => array
+        (
+            'type'      => 'varchar-800',
+            'name'      => '标清地址',
+            'default'   => '',
+            'desc'      => '标清地址',
+            'match'     => 'option',
+            //'update'    => 'text',
+            //'list'      => true,
+        ),
+
+        'url_hdl'      => array
+        (
+            'type'      => 'varchar-800',
+            'name'      => '普清地址',
+            'default'   => '',
+            'desc'      => '普清地址',
+            'match'     => 'option',
+            //'update'    => 'text',
+            //'list'      => true,
+        ),
+
+        'url_pic'      => array
+        (
+            'type'      => 'varchar-800',
+            'name'      => '直播截图地址',
+            'default'   => '',
+            'desc'      => '直播截图地址',
+            'match'     => 'option',
+            //'update'    => 'text',
+            //'list'      => true,
+        ),
+
+        'info'      => array
+        (
+            'type'      => 'text-255',
+            'name'      => '直播信息',
+            'default'   => '',
+            'desc'      => '直播信息',
+            'match'     => 'option',
+            //'update'    => 'text',
+            'list'      => 'Dever::load("live/lib/manage.liveInfo", {id})',
+        ),
+
+        'disabled'       => array
+        (
+            'type'      => 'int-11',
+            'name'      => '禁播流',
+            'default'   => '1',
+            'desc'      => '禁播流',
+            'match'     => 'is_numeric',
+            //'update'    => 'select',
+            'option'    => $disabled,
+            'search'    => 'select',
+            'list'      => true,
+            'edit'      => true,
+        ),
+
+        'state'     => array
+        (
+            'type'      => 'tinyint-1',
+            'name'      => '状态',
+            'default'   => '1',
+            'desc'      => '请选择状态',
+            'match'     => 'is_numeric',
+        ),
+        
+        'cdate'     => array
+        (
+            'type'      => 'int-11',
+            'name'      => '创建时间',
+            'match'     => array('is_numeric', time()),
+            'desc'      => '',
+            # 只有insert时才生效
+            'insert'    => true,
+            //'search'  => 'date',
+            //'list'      => 'date("Y-m-d H:i:s", {cdate})',
+        ),
+    ),
+
+    'manage' => array
+    (
+        //'excel'   => true,
+        'delete' => false,
+        'edit' => false,
+        //'insert' => false,
+
+        'button' => array
+        (
+            '同步所有流' => 'live/lib/manage.getStream',
+        ),
+
+        'list_button' => array
+        (
+            'list' => array('推流日志', '"stream_log&search_option_stream_id={id}&oper_parent=stream"'),
+        ),
+    ),
+
+    # request 请求接口定义
+    'request' => array
+    (
+        'getAll' => array
+        (
+            # 匹配的正则或函数 选填项
+            'option' => array
+            (
+                'status' => 'yes',
+                'online' => 'yes',
+                'key' => 'yes',
+                'state' => 1,
+            ),
+            'type' => 'all',
+            'order' => array('id' => 'desc'),
+            'col' => 'id,name,live,url_rtmp,url_hls,url_hdl,url_pic',
+        ),
+    )
+
+);

+ 174 - 0
database/stream_log.php

@@ -0,0 +1,174 @@
+<?php
+
+$live = function()
+{
+    $array = array();
+    $info = Dever::db('live/info')->state();
+    if($info)
+    {
+        $array += $info;
+    }
+    return $array;
+};
+
+$active = function()
+{
+    $array = array();
+    $info = Dever::db('live/active')->state();
+    if($info)
+    {
+        $array += $info;
+    }
+    return $array;
+};
+
+$stream = function()
+{
+    $array = array();
+    $info = Dever::db('live/stream')->state();
+    if($info)
+    {
+        $array += $info;
+    }
+    return $array;
+};
+
+return array
+(
+    # 表名
+    'name' => 'stream_log',
+    # 显示给用户看的名称
+    'lang' => '直播流日志',
+    'order' => 2,
+    'menu' => false,
+    # 数据结构
+    'struct' => array
+    (
+    
+        'id'        => array
+        (
+            'type'      => 'int-11',
+            'name'      => 'ID',
+            'default'   => '',
+            'desc'      => '',
+            'match'     => 'is_numeric',
+            'search'    => 'order',
+            'list'        => true,
+        ),
+
+        'live_id'       => array
+        (
+            'type'      => 'int-11',
+            'name'      => '所属直播云',
+            'default'   => '1',
+            'desc'      => '所属直播云',
+            'match'     => 'is_numeric',
+            'update'    => 'select',
+            //'option'    => $live,
+            'search'    => 'select',
+            //'list'      => true,
+        ),
+
+        'active_id'       => array
+        (
+            'type'      => 'int-11',
+            'name'      => '所属活动',
+            'default'   => '1',
+            'desc'      => '所属活动',
+            'match'     => 'is_numeric',
+            'update'    => 'select',
+            //'option'    => $active,
+            'search'    => 'select',
+            //'list'      => true,
+        ),
+
+        'stream_id'       => array
+        (
+            'type'      => 'int-11',
+            'name'      => '所属直播流',
+            'default'   => '1',
+            'desc'      => '所属直播流',
+            'match'     => 'is_numeric',
+            //'update'    => 'select',
+            'option'    => $stream,
+            //'search'    => 'select',
+            'list'      => true,
+        ),
+
+        'sdate'     => array
+        (
+            'type'      => 'int-11',
+            'name'      => '开始时间',
+            'match'     => 'is_numeric',
+            'default'   => '',
+            'desc'      => '',
+            'update'    => 'date',
+            'callback'  => 'maketime',
+            'list'      => '{sdate} > 0 ? date("Y-m-d H:i:s", {sdate}) : "未开始"',
+        ),
+
+        'edate'     => array
+        (
+            'type'      => 'int-11',
+            'name'      => '结束时间',
+            'match'     => 'is_numeric',
+            'default'   => '',
+            'desc'      => '',
+            //'update'    => 'date',
+            'callback'  => 'maketime',
+            'list'      => '{edate} > 0 ? date("Y-m-d H:i:s", {edate}) : "未结束"',
+        ),
+
+        'url_m3u8'      => array
+        (
+            'type'      => 'varchar-800',
+            'name'      => '直播回放地址',
+            'default'   => '',
+            'desc'      => '直播回放地址',
+            'match'     => 'option',
+            //'update'    => 'text',
+            'list'      => true,
+        ),
+
+        'url_mp4'      => array
+        (
+            'type'      => 'varchar-800',
+            'name'      => '直播回放地址',
+            'default'   => '',
+            'desc'      => '直播回放地址',
+            'match'     => 'option',
+            //'update'    => 'text',
+            'list'      => true,
+        ),
+
+        'state'     => array
+        (
+            'type'      => 'tinyint-1',
+            'name'      => '状态',
+            'default'   => '1',
+            'desc'      => '请选择状态',
+            'match'     => 'is_numeric',
+        ),
+        
+        'cdate'     => array
+        (
+            'type'      => 'int-11',
+            'name'      => '创建时间',
+            'match'     => array('is_numeric', time()),
+            'desc'      => '',
+            # 只有insert时才生效
+            'insert'    => true,
+            //'search'  => 'date',
+            //'list'      => 'date("Y-m-d H:i:s", {cdate})',
+        ),
+    ),
+
+    'manage' => array
+    (
+        //'excel'   => true,
+        'delete' => false,
+        'edit' => false,
+        //'insert' => false,
+    ),
+
+);

+ 8 - 0
index.php

@@ -0,0 +1,8 @@
+<?php
+
+define('DEVER_APP_NAME', 'live');
+define('DEVER_APP_LANG', '直播管理');
+define('DEVER_APP_PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR);
+define('DEVER_MANAGE_ORDER', 194);
+define('DEVER_MANAGE_ICON', 'glyphicon glyphicon-tower layui-icon-camera');
+include(DEVER_APP_PATH . '../boot.php');

+ 27 - 0
lib/Cron.php

@@ -0,0 +1,27 @@
+<?php
+namespace Live\Lib;
+
+use Dever;
+
+class Cron
+{
+    public function getStream_api()
+    {
+
+    }
+
+    # 定时获取 所有流的状态
+    public function getStream()
+    {
+        $data = Dever::db('live/stream')->state();
+
+        if ($data) {
+            foreach ($data as $k => $v) {
+                $handle = new Handle();
+                $data = $handle->get($v['id']);
+            }
+        }
+
+        return 'yes';
+    }
+}

+ 186 - 0
lib/Handle.php

@@ -0,0 +1,186 @@
+<?php
+namespace Live\Lib;
+
+use Dever;
+
+class Handle
+{
+    public function method($config)
+    {
+        if ($config['type'] == 1) {
+            $method = new Qiniu($config);
+        }
+
+        return $method;
+    }
+
+    # 启动
+    public function start($id)
+    {
+        $info = Dever::db('live/stream')->one($id);
+        $config = Dever::db('live/info')->one($info['live_id']);
+        $method = $this->method($config);
+        $stream = $method->start($info['key']);
+    }
+
+    # 禁播
+    public function stop($id)
+    {
+        $info = Dever::db('live/stream')->one($id);
+        $config = Dever::db('live/info')->one($info['live_id']);
+        $method = $this->method($config);
+        $stream = $method->stop($info['key']);
+    }
+
+    # 保存
+    public function save($id, $log_id)
+    {
+        $info = Dever::db('live/stream')->one($id);
+        $config = Dever::db('live/info')->one($info['live_id']);
+        $method = $this->method($config);
+
+        $info_update['where_id'] = $info['id'];
+        $info_update['status'] = 3;
+
+        Dever::db('live/stream')->update($info_update);
+
+        Dever::db('live/stream_log')->update(array('edate' => time(), 'where_id' => $log_id));
+
+        return true;
+    }
+
+    # 建立直播日志
+    public function saveLog($id, $log_id = false, $table = false)
+    {
+        $info = Dever::db('live/stream')->one($id);
+
+        if (!$log_id) {
+            $time = time();
+            $data['sdate'] = $time;
+            $data['stream_id'] = $id;
+            $data['live_id'] = $info['live_id'];
+            $data['active_id'] = $info['active_id'];
+            $id = Dever::db('live/stream_log')->insert($data);
+        } else {
+
+            $config = Dever::db('live/info')->one($info['live_id']);
+            $log = Dever::db('live/stream_log')->one($log_id);
+            $method = $this->method($config);
+            $log['sdate'] = intval($log['sdate']);
+            $log['edate'] = intval($log['edate']);
+            $data['url_m3u8'] = $config['host'] . $method->save($info['key'],$log['sdate'], $log['edate']);
+            $data['url_mp4'] = $config['host'] . $method->save($info['key'], $log['sdate'], $log['edate'], 'mp4');
+            $data['where_id'] = $log_id;
+            Dever::db('live/stream_log')->update($data);
+            $id = $log_id;
+
+            if ($table) {
+                $data = Dever::db($table)->state(array('stream_id' => $info['id']));
+                if ($data) {
+                    foreach ($data as $k => $v) {
+                        if (!$v['url_m3u8'] && $v['sdate'] > 0 && $v['edate'] > 0) {
+
+                            $v['sdate'] = intval($v['sdate']);
+                            $v['edate'] = intval($v['edate']);
+
+                            $url_m3u8 = $config['host'] . $method->save($info['key'],$v['sdate'], $v['edate']);
+                            $url_mp4 = $config['host'] . $method->save($info['key'], $v['sdate'], $v['edate'], 'mp4');
+
+                            Dever::db($table)->update(array('where_id' => $v['id'], 'url_m3u8' => $url_m3u8, 'url_mp4' => $url_mp4));
+                        }
+                    }
+                }
+            }
+        }
+        
+        return $id;
+    }
+
+    # 开始直播
+    public function startLive($id)
+    {
+        $info = Dever::db('live/stream')->one($id);
+        $config = Dever::db('live/info')->one($info['live_id']);
+        $method = $this->method($config);
+
+        $data['where_id'] = $info['id'];
+        $data['status'] = 2;
+
+        Dever::db('live/stream')->update($data);
+    }
+
+    # 初始化流
+    public function init($id, $name = '')
+    {
+        $info = Dever::db('live/stream')->one($id);
+        if ($info['url_rtmp']) {
+            return;
+        }
+        if ($name) {
+            $info['key'] = $name;
+        }
+
+        return $this->get($info, true);
+    }
+
+    # 获取流信息 定时跑
+    public function get($info, $create = false)
+    {
+        if (!is_array($info)) {
+            $info = Dever::db('live/stream')->one($info);
+        }
+
+        $config = Dever::db('live/info')->one($info['live_id']);
+        
+        $method = $this->method($config);
+
+        if ($create) {
+            $stream = $method->create($info['key']);
+        } else {
+            $stream = $method->getInfo($info['key']);
+        }
+
+        $rtmp = $method->getRtmp($info['key'], $info['times'] * 3600);
+
+        $url = $method->getPlay($info['key']);
+
+        $status = array();
+        $history = array();
+
+        try {
+            $status = $method->getLiveStatus($info['key']);
+        } catch (\Exception $e) {
+            $status = array();
+        }
+
+        try {
+            $history = $method->history($info['key']);
+        } catch (\Exception $e) {
+            $history = array();
+        }
+
+        $data['where_id'] = $info['id'];
+        $data['live'] = $rtmp;
+        $data['url_rtmp'] = $url['rtmp'];
+        $data['url_hls'] = $url['hls'];
+        $data['url_hdl'] = $url['hdl'];
+        $data['url_pic'] = $url['pic'];
+
+        $live = Dever::json_decode($info['info']);
+        $live['info'] = $stream;
+        $live['live'] = $status;
+        $live['hitory'] = $history;
+
+        if ($status) {
+            $data['status'] = 2;
+        } else {
+            $data['status'] = 1;
+        }
+
+        $data['info'] = Dever::json_encode($live);
+
+        Dever::db('live/stream')->update($data);
+
+        return $data;
+    }
+}

+ 125 - 0
lib/Manage.php

@@ -0,0 +1,125 @@
+<?php
+namespace Live\Lib;
+
+use Dever;
+
+class Manage
+{
+    # 获取所有的流
+	public function getStream_api()
+    {
+        $handle = new Handle();
+    	$info = Dever::db('live/active')->state();
+        foreach ($info as $k => $v) {
+            $live = Dever::db('live/info')->one($v['live_id']);
+            $data = $handle->method($live)->gets();
+            if (isset($data['keys']) && $data['keys']) {
+                foreach ($data['keys'] as $k1 => $v1) {
+                    $this->insertStream($v1, $live['id'], $v['id']);
+                }
+            }
+        }
+
+        return 'yes';
+    }
+
+    private function insertStream($name, $live_id, $active_id)
+    {
+        $data['key'] = $name;
+        $info = Dever::db('live/stream')->one($data);
+
+        if (!$info) {
+            $data['name'] = $name;
+            $data['active_id'] = $active_id;
+            $data['live_id'] = $live_id;
+            $id = Dever::db('live/stream')->insert($data);
+        } else {
+            $id = $info['id'];
+        }
+
+        $handle = new Handle();
+        $data = $handle->init($id, $name);
+    }
+
+    # 创建新流
+    public function createStream($id, $name, $data)
+    {
+        $name = Dever::param('key', $data);
+
+        $handle = new Handle();
+        $data = $handle->init($id, $name);
+    }
+
+    # 禁播流
+    public function updateDisabled($id, $name, $data)
+    {
+        $disabled = Dever::param('disabled', $data);
+
+        $handle = new Handle();
+        if ($disabled == 1) {
+            $handle->start($id);
+        } else {
+            $handle->stop($id);
+        }
+    }
+
+    # 获取流信息
+    public function liveData($id)
+    {
+        $info = Dever::db('live/stream')->one($id);
+
+        $html = $info['name'] . '('.$info['key'].')';
+        return $html;
+    }
+
+    # 获取直播地址
+    public function live($id)
+    {
+        $info = Dever::db('live/stream')->one($id);
+
+        $table['推流地址'] = $info['live'];
+        $table['高清地址'] = $info['url_rtmp'];
+        $table['标清地址'] = $info['url_hls'];
+        $table['普清地址'] = $info['url_hdl'];
+        $table['截图地址'] = $info['url_pic'];
+        return Dever::table($table);
+    }
+
+    # 获取直播信息
+    public function liveInfo($id)
+    {
+        $info = Dever::db('live/stream')->one($id);
+
+        $table = array();
+        if ($info['info']) {
+
+            if ($info['status'] == 1) {
+                $table['直播状态'] = '未开始';
+            } elseif ($info['status'] == 2) {
+                $table['直播状态'] = '直播中';
+            } elseif ($info['status'] == 3) {
+                $table['直播状态'] = '已结束';
+            }
+            $data = Dever::json_decode($info['info']);
+
+            if (isset($data['live']) && $data['live']) {
+                $table['开始时间'] = date('Y-m-d H:i:s', $data['live']['startAt']);
+                $table['主播IP'] = $data['live']['clientIP'];
+                $table['当前码率'] = $data['live']['bps'];
+                $table['音频帧率'] = $data['live']['fps']['audio'];
+                $table['视频帧率'] = $data['live']['fps']['video'];
+                $table['数据帧率'] = $data['live']['fps']['data'];
+
+            } 
+            if (isset($data['history']) && $data['history']) {
+                $history = array();
+                foreach ($data['history'] as $k => $v) {
+                    $history['第' . ($k+1) . '次直播'] = date('Y-m-d H:i:s', $v['start']) . '~' . date('Y-m-d H:i:s', $v['end']);
+                }
+                $table['直播历史'] = Dever::table($history);
+            }
+        }
+        
+        return Dever::table($table);
+    }
+}

+ 172 - 0
lib/Qiniu.php

@@ -0,0 +1,172 @@
+<?php
+namespace Live\Lib;
+
+use Dever;
+
+Dever::apply('sdk/qiniu', 'live');
+
+class Qiniu
+{
+    private $stream;
+    private $config;
+    private $space;
+
+    # 连接
+    public function __construct($config)
+    {
+        $this->config = $config;
+        $mac = new \Qiniu\Pili\Mac($this->config['appkey'], $this->config['appsecret']);
+        $client = new \Qiniu\Pili\Client($mac);
+        $this->space = $client->hub($this->config['space']);
+    }
+
+    # 创建新流
+    public function create($name)
+    {
+        $result = $this->space->create($name);
+
+        return $this->getInfo($name);
+    }
+
+    # 查询流
+    public function get($name)
+    {
+        if (!$this->stream) {
+            $this->stream = $this->space->stream($name);
+        }
+
+        return $this->stream;
+    }
+
+    # 查询流的信息
+    public function getInfo($name)
+    {
+        $stream = $this->get($name);
+
+        return $stream->info();
+    }
+
+    # 获取直播状态
+    public function getLiveStatus($name)
+    {
+        $stream = $this->get($name);
+
+        return $stream->liveStatus();
+    }
+
+    # 列出所有流
+    public function gets($prefix = '', $limit = 10000)
+    {
+        return $this->space->listStreams($prefix, $limit, '');
+    }
+
+    # 列出所有正在直播的流
+    public function getLives($prefix = '', $limit = 10000)
+    {
+        return $this->space->listLiveStreams($prefix, $limit, '');
+    }
+
+    # 启动流
+    public function start($name = '')
+    {
+        $stream = $this->get($name);
+        $stream->enable();
+        return $this->getLiveStatus($name);
+    }
+
+    # 禁用流
+    public function stop($name = '', $num = 120)
+    {
+        $stream = $this->get($name);
+        $stream->disable(time() + $num);
+        return $this->getLiveStatus($name);
+    }
+
+    # 保存直播数据
+    public function save($name, $start = 0, $end = 0, $format = false)
+    {
+        $stream = $this->get($name);
+        if ($end <= 0) {
+            $end = time();
+        }
+        if ($format) {
+            $format = array("format" => $format);
+            $data = $stream->saveas($format, $start, $end);
+        } else {
+            $data = $stream->save($start, $end);
+        }
+        
+        if (isset($data['fname'])) {
+            return $data['fname'];
+        }
+        return '';
+    }
+
+    # 保存直播截图
+    public function savePic($name)
+    {
+        $stream = $this->get($name);
+        return $stream->snapshot(array("format" => "jpg"));
+    }
+
+    # 查看历史直播流
+    public function history($name)
+    {
+        $stream = $this->get($name);
+        return $stream->historyActivity(0, 0);
+    }
+
+    # 查询推流历史
+    public function update($name, $convert)
+    {
+        $stream = $this->get($name);
+        $stream->updateConverts($convert);
+        return $stream->info();
+    }
+
+    # 获取推流地址
+    public function getRtmp($name, $time = 3600)
+    {
+        $url = \Qiniu\Pili\RTMPPublishURL($this->config['domain_publish'], $this->config['space'], $name, $time, $this->config['appkey'], $this->config['appsecret']);
+        return $url;
+    }
+
+    # 获取直播地址
+    public function getPlay($name)
+    {
+        //高清、标清、普清
+        $url['rtmp'] = $this->getRtmpPlay($name);
+        $url['hls'] = $this->getHlsPlay($name);
+        $url['hdl'] = $this->getHdlPlay($name);
+        $url['pic'] = $this->getPicPlay($name);
+        return $url;
+    }
+
+    # 获取直播地址
+    public function getRtmpPlay($name)
+    {
+        $url = \Qiniu\Pili\RTMPPlayURL($this->config['domain_live_rtmp'], $this->config['space'], $name);
+        return $url;
+    }
+
+    # HLS 直播地址
+    public function getHlsPlay($name)
+    {
+        $url = \Qiniu\Pili\HLSPlayURL($this->config['domain_live_hls'], $this->config['space'], $name);
+        return $url;
+    }
+
+    # HDL 直播地址
+    public function getHdlPlay($name)
+    {
+        $url = \Qiniu\Pili\HDLPlayURL($this->config['domain_live_hdl'], $this->config['space'], $name);
+        return $url;
+    }
+
+    # 截图直播地址
+    public function getPicPlay($name)
+    {
+        $url = \Qiniu\Pili\SnapshotPlayURL($this->config['domain_live_pic'], $this->config['space'], $name);
+        return $url;
+    }
+}

+ 18 - 0
sdk/Qiniu/Pili/Client.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Qiniu\Pili;
+
+class Client
+{
+    private $_mac;
+
+    public function __construct($mac)
+    {
+        $this->_mac=$mac;
+    }
+
+    public function hub($hubname)
+    {
+        return new Hub($this->_mac, $hubname);
+    }
+}

+ 50 - 0
sdk/Qiniu/Pili/Config.php

@@ -0,0 +1,50 @@
+<?php
+namespace Qiniu\Pili;
+
+final class Config
+{
+    const SDK_VERSION = '2.1.1';
+    const SDK_USER_AGENT = 'pili-sdk-php';
+
+    public $USE_HTTPS = false;
+    public $API_HOST = 'pili.qiniuapi.com';
+    public $API_VERSION = 'v2';
+
+    public $RTCAPI_HOST = 'http://rtc.qiniuapi.com';
+    public $RTCAPI_VERSION = 'v2';   //连麦版本号,可以设置 "v1" 和 "v2"
+
+    protected static $_instance = null;
+
+    protected function __construct()
+    {
+    }
+
+    protected function __clone()
+    {
+    }
+
+    public static function getInstance()
+    {
+        if (!(self::$_instance instanceof self)) {
+            self::$_instance = new self();
+        }
+        return self::$_instance;
+    }
+
+    public function __get($property)
+    {
+        if (property_exists(self::getInstance(), $property)) {
+            return self::getInstance()->$property;
+        } else {
+            return null;
+        }
+    }
+
+    public function __set($property, $value)
+    {
+        if (property_exists(self::getInstance(), $property)) {
+            self::getInstance()->$property = $value;
+        }
+        return self::getInstance();
+    }
+}

+ 178 - 0
sdk/Qiniu/Pili/HttpRequest.php

@@ -0,0 +1,178 @@
+<?php
+namespace Qiniu\Pili;
+
+use \Qiniu\Pili\HttpResponse;
+
+class HttpRequest
+{
+    const DELETE = "DELETE";
+    const GET    = "GET";
+    const POST   = "POST";
+
+    private static $verifyPeer = false;
+    private static $socketTimeout = null;
+    private static $defaultHeaders = array();
+
+    /**
+     * Verify SSL peer
+     * @param bool $enabled enable SSL verification, by default is false
+     */
+    public static function verifyPeer($enabled)
+    {
+        self::$verifyPeer = $enabled;
+    }
+
+    /**
+     * Set a timeout
+     * @param integer $seconds timeout value in seconds
+     */
+    public static function timeout($seconds)
+    {
+        self::$socketTimeout = $seconds;
+    }
+
+    /**
+     * Set a new default header to send on every request
+     * @param string $name header name
+     * @param string $value header value
+     */
+    public static function defaultHeader($name, $value)
+    {
+        self::$defaultHeaders[$name] = $value;
+    }
+
+    /**
+     * Clear all the default headers
+     */
+    public static function clearDefaultHeaders()
+    {
+        self::$defaultHeaders = array();
+    }
+
+    /**
+     * This function is useful for serializing multidimensional arrays, and avoid getting
+     * the "Array to string conversion" notice
+     */
+    public static function http_build_query_for_curl($arrays, &$new = array(), $prefix = null)
+    {
+        if (is_object($arrays)) {
+            $arrays = get_object_vars($arrays);
+        }
+        foreach ($arrays as $key => $value) {
+            $k = isset($prefix) ? $prefix . '[' . $key . ']' : $key;
+            if (!$value instanceof \CURLFile and (is_array($value) or is_object($value))) {
+                self::http_build_query_for_curl($value, $new, $k);
+            } else {
+                $new[$k] = $value;
+            }
+        }
+    }
+
+    private static function getArrayFromQuerystring($querystring)
+    {
+        $pairs = explode("&", $querystring);
+        $vars  = array();
+        foreach ($pairs as $pair) {
+            $nv          = explode("=", $pair, 2);
+            $name        = $nv[0];
+            $value       = $nv[1];
+            $vars[$name] = $value;
+        }
+        return $vars;
+    }
+
+    /**
+     * Ensure that a URL is encoded and safe to use with cURL
+     * @param  string $url URL to encode
+     * @return string
+     */
+    private static function encodeUrl($url)
+    {
+        $url_parsed = parse_url($url);
+        $scheme = $url_parsed['scheme'] . '://';
+        $host   = $url_parsed['host'];
+        $port   = (isset($url_parsed['port']) ? $url_parsed['port'] : null);
+        $path   = (isset($url_parsed['path']) ? $url_parsed['path'] : null);
+        $query  = (isset($url_parsed['query']) ? $url_parsed['query'] : null);
+        if ($query != null) {
+            $query = '?' . http_build_query(self::getArrayFromQuerystring($url_parsed['query']));
+        }
+        if ($port && $port[0] != ":") {
+            $port = ":" . $port;
+        }
+        $result = $scheme . $host . $port . $path . $query;
+        return $result;
+    }
+
+    private static function getHeader($key, $val)
+    {
+        $key = trim($key);
+        return $key . ": " . $val;
+    }
+
+    /**
+     * Send a cURL request
+     * @param string $httpMethod HTTP method to use
+     * @param string $url URL to send the request to
+     * @param mixed $body request body
+     * @param array $headers additional headers to send
+     * @throws Exception if a cURL error occurs
+     * @return HttpResponse
+     */
+    public static function send($httpMethod, $url, $body = null, $headers = array())
+    {
+        if ($headers == null) {
+            $headers = array();
+        }
+        $annexHeaders = array();
+        $finalHeaders = array_merge($headers, self::$defaultHeaders);
+        foreach ($finalHeaders as $key => $val) {
+            $annexHeaders[] = self::getHeader($key, $val);
+        }
+        $lowerCaseFinalHeaders = array_change_key_case($finalHeaders);
+        $ch = curl_init();
+        if ($httpMethod != self::GET) {
+            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $httpMethod);
+            if (is_array($body) || $body instanceof Traversable) {
+                self::http_build_query_for_curl($body, $postBody);
+                curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
+            } else {
+                curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
+            }
+        } elseif (is_array($body)) {
+            if (strpos($url, '?') !== false) {
+                $url .= "&";
+            } else {
+                $url .= "?";
+            }
+            self::http_build_query_for_curl($body, $postBody);
+            $url .= urldecode(http_build_query($postBody));
+        }
+        curl_setopt($ch, CURLOPT_URL, self::encodeUrl($url));
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $annexHeaders);
+        curl_setopt($ch, CURLOPT_HEADER, true);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, self::$verifyPeer);
+        curl_setopt($ch, CURLOPT_ENCODING, ""); // If an empty string, "", is set, a header containing all supported encoding types is sent.
+        if (self::$socketTimeout != null) {
+            curl_setopt($ch, CURLOPT_TIMEOUT, self::$socketTimeout);
+        }
+        $response = curl_exec($ch);
+        $error    = curl_error($ch);
+        if ($error) {
+            throw new \Exception($error);
+        }
+        // Split the full response in its headers and body
+        $curl_info   = curl_getinfo($ch);
+        $header_size = $curl_info["header_size"];
+        $header      = substr($response, 0, $header_size);
+        $body        = substr($response, $header_size);
+        $httpCode    = $curl_info["http_code"];
+        if ($httpCode >= 400) {
+            throw new \Exception($body);
+        }
+        return new HttpResponse($httpCode, $body, $header);
+    }
+}

+ 71 - 0
sdk/Qiniu/Pili/HttpResponse.php

@@ -0,0 +1,71 @@
+<?php
+namespace Qiniu\Pili;
+
+class HttpResponse
+{
+    private $code;
+    private $raw_body;
+    private $body;
+    private $headers;
+
+    /**
+     * @param int $code response code of the cURL request
+     * @param string $raw_body the raw body of the cURL response
+     * @param string $headers raw header string from cURL response
+     */
+    public function __construct($code, $raw_body, $headers)
+    {
+        $this->code     = $code;
+        $this->headers  = $this->get_headers_from_curl_response($headers);
+        $this->raw_body = $raw_body;
+        $this->body     = $raw_body;
+        $json           = json_decode($raw_body, true);
+        if (json_last_error() == JSON_ERROR_NONE) {
+            $this->body = $json;
+        }
+    }
+
+    /**
+     * Return a property of the response if it exists.
+     * Possibilities include: code, raw_body, headers, body (if the response is json-decodable)
+     * @return mixed
+     */
+    public function __get($property)
+    {
+        if (property_exists($this, $property)) {
+            return $this->$property;
+        }
+    }
+
+    /**
+     * Set the properties of this object
+     * @param string $property the property name
+     * @param mixed $value the property value
+     */
+    public function __set($property, $value)
+    {
+        if (property_exists($this, $property)) {
+            $this->$property = $value;
+        }
+        return $this;
+    }
+
+    /**
+     * Retrieve the cURL response headers from the
+     * header string and convert it into an array
+     * @param  string $headers header string from cURL response
+     * @return array
+     */
+    private function get_headers_from_curl_response($headers)
+    {
+        $headers = explode("\r\n", $headers);
+        array_shift($headers);
+        foreach ($headers as $line) {
+            if (strstr($line, ': ')) {
+                list($key, $value) = explode(': ', $line);
+                $result[$key] = $value;
+            }
+        }
+        return $result;
+    }
+}

+ 159 - 0
sdk/Qiniu/Pili/Hub.php

@@ -0,0 +1,159 @@
+<?php
+namespace Qiniu\Pili;
+
+use \Qiniu\Pili\HttpRequest;
+use \Qiniu\Pili\Utils;
+
+class Hub
+{
+    private $_hub;
+    private $_baseURL;
+    private $_transport;
+
+    public function __construct($mac, $hubName)
+    {
+        $this->_hub = $hubName;
+        $this->_transport = new Transport($mac);
+
+        $cfg = Config::getInstance();
+        $protocal = $cfg->USE_HTTPS === true ? "https" : "http";
+        $this->_baseURL = $protocal . "://" . $cfg->API_HOST . "/v2/hubs/" . $this->_hub;
+    }
+
+    //创建一个流对象.
+    /*
+     * PARAM
+     * @streamKey: 流名.
+     * RETURN
+     * 返回一个流对象.
+     */
+    public function create($streamKey)
+    {
+        $url = $this->_baseURL . "/streams";
+        $params['key'] = $streamKey;
+        $body = json_encode($params);
+        try {
+            $this->_transport->send(HttpRequest::POST, $url, $body);
+        } catch (\Exception $e) {
+            return $e;
+        }
+
+        return new Stream($this->_transport, $this->_hub, $streamKey);
+    }
+
+    //初始化一个流对象
+    /*
+     * PARAM
+     * @streamKey: 流名.
+     * RETURN
+     * 返回一个流对象.
+     */
+    public function stream($streamKey)
+    {
+        return new Stream($this->_transport, $this->_hub, $streamKey);
+    }
+
+    private function _list($live, $prefix, $limit, $marker)
+    {
+        $url = sprintf("%s/streams?liveonly=%s&prefix=%s&limit=%d&marker=%s", $this->_baseURL, $live, $prefix, $limit, $marker);
+        try {
+            $ret = $this->_transport->send(HttpRequest::GET, $url);
+        } catch (\Exception $e) {
+            return $e;
+        }
+        $keys = array();
+        foreach ($ret["items"] as $item) {
+            array_push($keys, $item["key"]);
+        }
+        $marker = $ret["marker"];
+        $ret = array();
+        $ret["keys"] = $keys;
+        $ret["omarker"] = $marker;
+        return $ret;
+    }
+
+    //根据 prefix 遍历 Hub 的正在直播的流列表.
+    /*
+     * PARAM
+     * @prefix: 流名的前缀.
+     * @limit: 限定了一次最多可以返回的流个数, 实际返回的流个数可能小于这个 limit 值.
+     * @marker: 是上一次遍历得到的流标.
+     * RETURN
+     * @keys: 流名的数组.
+     * @omarker: 记录了此次遍历到的游标, 在下次请求时应该带上, 如果 omarker 为 "" 表示已经遍历完所有流.
+     */
+    public function listLiveStreams($prefix, $limit, $marker)
+    {
+        return $this->_list("true", $prefix, $limit, $marker);
+    }
+
+    //根据 prefix 遍历 Hub 的流列表.
+    /*
+     * PARAM
+     * @prefix: 流名的前缀.
+     * @limit: 限定了一次最多可以返回的流个数, 实际返回的流个数可能小于这个 limit 值.
+     * @marker: 是上一次遍历得到的流标.
+     * RETURN
+     * @keys: 流名的数组.
+     * @omarker: 记录了此次遍历到的游标, 在下次请求时应该带上, 如果 omarker 为 "" 表示已经遍历完所有流.
+     */
+    public function listStreams($prefix, $limit, $marker)
+    {
+        return $this->_list("false", $prefix, $limit, $marker);
+    }
+
+    //批量查询流直播信息.
+    /*
+     * PARAM
+     * @streamKeys: 流名数组, 最大长度为100.
+     * RETURN
+     * @items: 数组. 每个item包含一个流的直播信息.
+     *   @key: 流名.
+     *   @startAt: 直播开始的 Unix 时间戳, 0 表示当前没在直播.
+     *   @clientIP: 直播的客户端 IP.
+     *   @bps: 直播的码率.
+     *   @fps: 直播的帧率.
+     */
+    public function batchLiveStatus($streamKeys)
+    {
+        $url = $this->_baseURL . "/livestreams";
+        $params['items'] = $streamKeys;
+        $body = json_encode($params);
+        return $this->_transport->send(HttpRequest::POST, $url, $body);
+    }
+}
+
+//----------------url
+//生成 RTMP 推流地址.
+//expireAfterSeconds 表示 URL 在多久之后失效.
+function RTMPPublishURL($domain, $hub, $streamKey, $expireAfterSeconds, $accessKey, $secretKey)
+{
+    $expire = time() + $expireAfterSeconds;
+    $path = sprintf("/%s/%s?e=%d", $hub, $streamKey, $expire);
+    $token = $accessKey . ":" . Utils::sign($secretKey, $path);
+    return sprintf("rtmp://%s%s&token=%s", $domain, $path, $token);
+}
+
+//生成 RTMP 直播地址.
+function RTMPPlayURL($domain, $hub, $streamKey)
+{
+    return sprintf("rtmp://%s/%s/%s", $domain, $hub, $streamKey);
+}
+
+//生成 HLS 直播地址.
+function HLSPlayURL($domain, $hub, $streamKey)
+{
+    return sprintf("http://%s/%s/%s.m3u8", $domain, $hub, $streamKey);
+}
+
+//生成 HDL 直播地址.
+function HDLPlayURL($domain, $hub, $streamKey)
+{
+    return sprintf("http://%s/%s/%s.flv", $domain, $hub, $streamKey);
+}
+
+//生成直播封面地址.
+function SnapshotPlayURL($domain, $hub, $streamKey)
+{
+    return sprintf("http://%s/%s/%s.jpg", $domain, $hub, $streamKey);
+}

+ 40 - 0
sdk/Qiniu/Pili/Mac.php

@@ -0,0 +1,40 @@
+<?php
+namespace Qiniu\Pili;
+
+class Mac
+{
+    public $_accessKey;
+    public $_secretKey;
+
+    public function __construct($accessKey, $secretKey)
+    {
+        $this->_accessKey = $accessKey;
+        $this->_secretKey = $secretKey;
+    }
+
+    public function MACToken($method, $url, $contentType, $body)
+    {
+        $url = parse_url($url);
+        $data = '';
+        if (!empty($url['path'])) {
+            $data = $method . ' ' . $url['path'];
+        }
+        if (!empty($url['query'])) {
+            $data .= '?' . $url['query'];
+        }
+        if (!empty($url['host'])) {
+            $data .= "\nHost: " . $url['host'];
+            if (isset($url['port'])) {
+                $data .= ':' . $url['port'];
+            }
+        }
+        if (!empty($contentType)) {
+            $data .= "\nContent-Type: " . $contentType;
+        }
+        $data .= "\n\n";
+        if (!empty($body)) {
+            $data .= $body;
+        }
+        return 'Qiniu ' . $this->_accessKey . ':' . Utils::sign($this->_secretKey, $data);
+    }
+}

+ 119 - 0
sdk/Qiniu/Pili/RoomClient.php

@@ -0,0 +1,119 @@
+<?php
+namespace Qiniu\Pili;
+
+class RoomClient
+{
+    private $_transport;
+    private $_mac;
+    private $_baseURL;
+
+    public function __construct($mac)
+    {
+        $this->_mac = $mac;
+        $this->_transport = new Transport($mac);
+
+        $cfg = Config::getInstance();
+        $this->_baseURL = sprintf("%s/%s/rooms", $cfg->RTCAPI_HOST, $cfg->RTCAPI_VERSION);
+    }
+
+    /*
+     * ownerId: 要创建房间的所有者
+     * roomName: 房间名称
+     */
+    public function createRoom($ownerId, $roomName = null)
+    {
+        $params['owner_id'] = $ownerId;
+        if (!empty($roomName)) {
+            $params['room_name'] = $roomName;
+        }
+        $body = json_encode($params);
+        try {
+            $ret = $this->_transport->send(HttpRequest::POST, $this->_baseURL, $body);
+        } catch (\Exception $e) {
+            return $e;
+        }
+        return $ret;
+    }
+
+    /*
+     * roomName: 房间名称
+     */
+    public function getRoom($roomName)
+    {
+        $url = $this->_baseURL . '/' . $roomName;
+        try {
+            $ret = $this->_transport->send(HttpRequest::GET, $url);
+        } catch (\Exception $e) {
+            return $e;
+        }
+        return $ret;
+    }
+
+    /*
+     * roomName: 房间名称
+     */
+    public function deleteRoom($roomName)
+    {
+        $url = $this->_baseURL . '/' . $roomName;
+        try {
+            $ret = $this->_transport->send(HttpRequest::DELETE, $url);
+        } catch (\Exception $e) {
+            return $e;
+        }
+        return $ret;
+    }
+
+    /*
+     * 获取房间的人数
+     * roomName: 房间名称
+     */
+    public function getRoomUserNum($roomName)
+    {
+        $url = sprintf("%s/%s/users", $this->_baseURL, $roomName);
+        try {
+            $ret = $this->_transport->send(HttpRequest::GET, $url);
+        } catch (\Exception $e) {
+            return $e;
+        }
+        return $ret;
+    }
+
+   /*
+    * 踢出玩家
+    * roomName: 房间名称
+    * userId: 请求加入房间的用户ID
+    */
+    public function kickingPlayer($roomName, $UserId)
+    {
+        $url = sprintf("%s/%s/users/%s", $this->_baseURL, $roomName, $UserId);
+        try {
+            $ret = $this->_transport->send(HttpRequest::DELETE, $url);
+        } catch (\Exception $e) {
+            return $e;
+        }
+        return $ret;
+    }
+
+    /*
+     * roomName: 房间名称
+     * userId: 请求加入房间的用户ID
+     * perm: 该用户的房间管理权限,"admin""user",房间主播为"admin",拥有将其他用户移除出房间等特权。
+     * expireAt: int64类型,鉴权的有效时间,传入秒为单位的64位Unix时间,token将在该时间后失效。
+     */
+    public function roomToken($roomName, $userId, $perm, $expireAt)
+    {
+        $ver = Config::getInstance()->RTCAPI_VERSION;
+        if ($ver === 'v2') {
+            $params['version']="2.0";
+        }
+        $params['room_name'] = $roomName;
+        $params['user_id'] = $userId;
+        $params['perm'] = $perm;
+        $params['expire_at'] = $expireAt;
+        $roomAccessString = json_encode($params);
+        $encodedRoomAccess = Utils::base64UrlEncode($roomAccessString);
+        $sign = hash_hmac('sha1', $encodedRoomAccess, $this->_mac->_secretKey, true);
+        $encodedSign = Utils::base64UrlEncode($sign);
+        return $this->_mac->_accessKey . ":" . $encodedSign . ":" . $encodedRoomAccess;
+    }
+}

+ 189 - 0
sdk/Qiniu/Pili/Stream.php

@@ -0,0 +1,189 @@
+<?php
+namespace Qiniu\Pili;
+
+use \Qiniu\Pili\Utils;
+use \Qiniu\Pili\HttpRequest;
+
+class Stream
+{
+    private $_transport;
+    private $_hub;
+    private $_key;
+    private $_baseURL;
+
+    public function __construct($transport, $hub, $key)
+    {
+        $this->_transport = $transport;
+        $this->_hub = $hub;
+        $this->_key = $key;
+
+        $cfg = Config::getInstance();
+        $protocal = $cfg->USE_HTTPS === true ? "https" : "http";
+        $this->_baseURL = sprintf("%s://%s/%s/hubs/%s/streams/%s", $protocal, $cfg->API_HOST, $cfg->API_VERSION, $this->_hub, Utils::base64UrlEncode($this->_key));
+    }
+
+    //获得流信息.
+    /*
+     * RETURN
+     * @hub: Hub名.
+     * @key: 流名.
+     * @disableTill: 禁用结束的时间, 0 表示不禁用, -1 表示永久禁用.
+     * @converts: 实时转码规格.
+    */
+    public function info()
+    {
+        $resp=$this->_transport->send(HttpRequest::GET, $this->_baseURL);
+        $ret = array();
+        $ret["hub"] = $this->_hub;
+        $ret["key"] = $this->_key;
+        $ret["disabledTill"] = $resp["disabledTill"];
+        $ret["converts"] = $resp["converts"];
+        return $ret;
+    }
+
+    //在一定期限内禁止一个流.
+    /*
+     * PARAM
+     * @till: Unix 时间戳, 在这之前流均不可用.
+     */
+    public function disable($till = null)
+    {
+        $url = $this->_baseURL . "/disabled";
+        if (empty($till)) {
+            $params['disabledTill'] = -1;
+        } else {
+            $params['disabledTill'] = $till;
+        }
+        $body = json_encode($params);
+        return $this->_transport->send(HttpRequest::POST, $url, $body);
+    }
+
+    //启用一个流.
+    public function enable()
+    {
+        $url = $this->_baseURL . "/disabled";
+        $params['disabledTill'] = 0;
+        $body = json_encode($params);
+        return $this->_transport->send(HttpRequest::POST, $url, $body);
+    }
+
+    //查询直播状态.
+    /*
+     * RETURN
+     * @startAt: 直播开始的 Unix 时间戳, 0 表示当前没在直播.
+     * @clientIP: 直播的客户端 IP.
+     * @bps: 直播的码率.
+     * @fps: 直播的帧率.
+     */
+    public function liveStatus()
+    {
+        $url = $this->_baseURL . "/live";
+        return $this->_transport->send(HttpRequest::GET, $url);
+    }
+
+    //查询直播历史.
+    /*
+     * PARAM
+     * @start: Unix 时间戳, 限定了查询的时间范围, 0 值表示不限定, 系统会返回所有时间的直播历史.
+     * @end: Unix 时间戳, 限定了查询的时间范围, 0 值表示不限定, 系统会返回所有时间的直播历史.
+     * RETURN
+     * @items: 数组. 每个item包含一次推流的开始及结束时间.
+     *   @start: Unix 时间戳, 直播开始时间.
+     *   @end: Unix 时间戳, 直播结束时间.
+     */
+    public function historyActivity($start = null, $end = null)
+    {
+        $url = $this->_baseURL . "/historyrecord";
+        $flag = "?";
+        if (!empty($start)) {
+            $url = $url . $flag . "start=" . $start;
+            $flag = "&";
+        }
+        if (!empty($end)) {
+            $url = $url . $flag . "end=" . $end;
+        }
+        return $this->_transport->send(HttpRequest::GET, $url);
+    }
+
+    //保存直播回放.
+    /*
+     * PARAM
+     * @start: Unix 时间戳, 起始时间, 0 值表示不指定, 则不限制起始时间.
+     * @end: Unix 时间戳, 结束时间, 0 值表示当前时间.
+     * RETURN
+     * @fname: 保存到bucket里的文件名, 由系统生成.
+     */
+    public function save($start = null, $end = null)
+    {
+        $url = $this->_baseURL . "/saveas";
+        if (!empty($start)) {
+            $params['start'] = $start;
+        }
+        if (!empty($end)) {
+            $params['end'] = $end;
+        }
+        $body = json_encode($params);
+        return $this->_transport->send(HttpRequest::POST, $url, $body);
+    }
+
+    //灵活度更高的保存直播回放.
+    /*
+     * PARAM
+     * @fname: 保存的文件名, 不指定会随机生成.
+     * @start: Unix 时间戳, 起始时间, 0 值表示不指定, 则不限制起始时间.
+     * @end: Unix 时间戳, 结束时间, 0 值表示当前时间.
+     * @format: 保存的文件格式, 默认为m3u8.
+     * @pipeline: dora 的私有队列, 不指定则用默认队列.
+     * @notify: 保存成功后的回调地址.
+     * @expireDays: 对应ts文件的过期时间.
+     *   -1 表示不修改ts文件的expire属性.
+     *   0  表示修改ts文件生命周期为永久保存.
+     *   >0 表示修改ts文件的的生命周期为ExpireDays.
+     * RETURN
+     * @fname: 保存到bucket里的文件名.
+     * @persistentID: 异步模式时,持久化异步处理任务ID,通常用不到该字段.
+     */
+    public function saveas($options = null)
+    {
+        $url = $this->_baseURL . "/saveas";
+        if (!empty($options)) {
+            $body = json_encode($options);
+        } else {
+            $body = null;
+        }
+        return $this->_transport->send(HttpRequest::POST, $url, $body);
+    }
+
+    //对流进行截图并保存.
+    /*
+     * PARAM
+     * @fname: 保存的文件名, 不指定会随机生成.
+     * @time: Unix 时间戳, 保存的时间点, 默认为当前时间.
+     * @format: 保存的文件格式, 默认为jpg.
+     * RETURN
+     * @fname: 保存到bucket里的文件名.
+     */
+    public function snapshot($options = null)
+    {
+        $url = $this->_baseURL . "/snapshot";
+        if (!empty($options)) {
+            $body = json_encode($options);
+        } else {
+            $body = null;
+        }
+        return $this->_transport->send(HttpRequest::POST, $url, $body);
+    }
+
+    //更改流的实时转码规格
+    /*
+     * PARAM
+     * @profiles: 实时转码规格. array("480p", "720p")
+     */
+    public function updateConverts($profiles)
+    {
+        $url = $this->_baseURL . "/converts";
+        $params['converts'] = $profiles;
+        $body = json_encode($params);
+        return $this->_transport->send(HttpRequest::POST, $url, $body);
+    }
+}

+ 38 - 0
sdk/Qiniu/Pili/Transport.php

@@ -0,0 +1,38 @@
+<?php
+namespace Qiniu\Pili;
+
+use \Qiniu\Pili\Utils;
+use \Qiniu\Pili\HttpRequest;
+
+final class Transport
+{
+    private $_mac;
+
+    public function __construct($mac)
+    {
+        $this->_mac = $mac;
+    }
+
+    public function send($method, $url, $body = null)
+    {
+        $headers = $this->_setHeaders($method, $url, $body);
+        $response = HttpRequest::send($method, $url, $body, $headers);
+        return $response->body;
+    }
+
+    private function _setHeaders($method, $url, $body = null)
+    {
+        if ($method != HttpRequest::GET) {
+            $cType = 'application/json';
+        } else {
+            $cType = null;
+        }
+        $macToken = $this->_mac->MACToken($method, $url, $cType, $body);
+        $ua = Utils::getUserAgent(Config::SDK_USER_AGENT, Config::SDK_VERSION);
+        return array(
+            'Content-Type'  => $cType,
+            'User-Agent'    => $ua,
+            'Authorization' => $macToken,
+        );
+    }
+}

+ 40 - 0
sdk/Qiniu/Pili/Utils.php

@@ -0,0 +1,40 @@
+<?php
+namespace Qiniu\Pili;
+
+final class Utils
+{
+    public static function base64UrlEncode($str)
+    {
+        $find = array('+', '/');
+        $replace = array('-', '_');
+        return str_replace($find, $replace, base64_encode($str));
+    }
+
+    public static function base64UrlDecode($str)
+    {
+        $find = array('-', '_');
+        $replace = array('+', '/');
+        return base64_decode(str_replace($find, $replace, $str));
+    }
+
+    public static function digest($secret, $data)
+    {
+        return hash_hmac('sha1', $data, $secret, true);
+    }
+
+    public static function sign($secret, $data)
+    {
+        return self::base64UrlEncode(self::digest($secret, $data));
+    }
+
+    public static function getUserAgent($sdkName, $sdkVersion)
+    {
+        $ua = sprintf("%s/%s", $sdkName, $sdkVersion);
+        if (extension_loaded('curl')) {
+            $curlVersion = curl_version();
+            $ua .= ' curl/' . $curlVersion['version'];
+        }
+        $ua .= ' PHP/' . PHP_VERSION;
+        return $ua;
+    }
+}

+ 14 - 0
sdk/Qiniu/Pili_v2.php

@@ -0,0 +1,14 @@
+<?php
+
+$root = dirname(__FILE__);
+
+require(join(DIRECTORY_SEPARATOR, array($root, 'Pili', 'Utils.php')));
+require(join(DIRECTORY_SEPARATOR, array($root, 'Pili', 'HttpResponse.php')));
+require(join(DIRECTORY_SEPARATOR, array($root, 'Pili', 'HttpRequest.php')));
+require(join(DIRECTORY_SEPARATOR, array($root, 'Pili', 'Mac.php')));
+require(join(DIRECTORY_SEPARATOR, array($root, 'Pili', 'Config.php')));
+require(join(DIRECTORY_SEPARATOR, array($root, 'Pili', 'Transport.php')));
+require(join(DIRECTORY_SEPARATOR, array($root, 'Pili', 'Hub.php')));
+require(join(DIRECTORY_SEPARATOR, array($root, 'Pili', 'Stream.php')));
+require(join(DIRECTORY_SEPARATOR, array($root, 'Pili', 'Client.php')));
+require(join(DIRECTORY_SEPARATOR, array($root, 'Pili', 'RoomClient.php')));

+ 3 - 0
sdk/qiniu.php

@@ -0,0 +1,3 @@
+<?php
+
+Dever::apply('sdk/Qiniu/Pili_v2', 'live');

+ 100 - 0
src/Api.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace Live\Src;
+
+use Dever;
+use Live\Lib\Handle;
+class Api
+{
+	private $key = 'signature_dever_2018_jm';
+
+	private function check()
+	{
+		$signature = Dever::input('signature');
+        if ($signature != md5($this->key)) {
+            Dever::alert('signature error');
+        }
+	}
+
+    # 获取直播地址
+    public function get()
+    {
+        $this->check();
+
+        $data = Dever::db('live/active')->getAll(array('status' => 2));
+
+        if ($data) {
+            foreach ($data as $k => $v) {
+                $data[$k]['stream'] = Dever::db('live/stream')->getAll();
+                if ($data[$k]['stream']) {
+                	foreach ($data[$k]['stream'] as $k1 => $v1) {
+                		if (!$v1['url_rtmp']) {
+                			unset($data[$k]['stream'][$k1]);
+                		}
+                	}
+                }
+            }
+            return array('active' => $data);
+        } else {
+            return array('active' => (object) array());
+        }
+    }
+
+    # 开始推流
+    public function start()
+    {
+        $this->check();
+        $id = Dever::input('id');
+        if (!$id) {
+        	Dever::alert('错误的id');
+        }
+        $handle = new Handle();
+        $handle->startLive($id);
+
+        # 建立一个推流日志
+        $log_id = $handle->saveLog($id);
+
+        return array('id' => $id, 'log_id' => $log_id);
+    }
+
+    # 结束推流
+    public function stop()
+    {
+        $this->check();
+        $id = Dever::input('id');
+        $log_id = Dever::input('log_id');
+        if (!$id) {
+        	Dever::alert('错误的id');
+        }
+        if (!$log_id) {
+        	Dever::alert('错误的id');
+        }
+        $handle = new Handle();
+        $handle->save($id, $log_id);
+
+        Dever::daemon('api.save?id=' . $id . '&log_id=' . $log_id, 'live');
+
+        //Dever::load('live/api.save?id=' . $id . '&log_id=' . $log_id);
+        return array('id' => $id, 'log_id' => $log_id);
+    }
+
+    # 获取回放地址
+    public function save()
+    {
+        $id = Dever::input('id');
+        $log_id = Dever::input('log_id');
+        if (!$id) {
+        	Dever::alert('错误的id');
+        }
+        if (!$log_id) {
+        	Dever::alert('错误的id');
+        }
+        $handle = new Handle();
+        $handle->saveLog($id, $log_id, 'video/live');
+
+        # 获取这个流对应的直播信息
+        
+
+        return 'ok';
+    }
+}