1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-07-02 07:04:56 +08:00

完成直播回调

This commit is contained in:
xiaochong0302 2020-08-30 19:56:20 +08:00
parent 065b910b33
commit c22417913a
21 changed files with 217 additions and 101 deletions

View File

@ -66,7 +66,8 @@ class VodEventTask extends Task
*/
$attrs = $chapter->attrs;
$attrs['file_status'] = ChapterModel::FS_TRANSLATING;
$attrs['file']['status'] = ChapterModel::FS_TRANSLATING;
$attrs['duration'] = (int)$duration;
$chapter->update(['attrs' => $attrs]);
@ -118,7 +119,7 @@ class VodEventTask extends Task
*/
$attrs = $chapter->attrs;
$attrs['file_status'] = $fileStatus;
$attrs['file']['status'] = $fileStatus;
$chapter->update(['attrs' => $attrs]);
}

View File

@ -181,20 +181,6 @@ class TestController extends Controller
$this->view->setVar('qrcode', $qrcode);
}
/**
* @Get("/alipay/status", name="admin.test.alipay_status")
*/
public function alipayStatusAction()
{
$sn = $this->request->getQuery('sn');
$alipayTestService = new AlipayTestService();
$status = $alipayTestService->status($sn);
return $this->jsonSuccess(['status' => $status]);
}
/**
* @Get("/wxpay", name="admin.test.wxpay")
*/
@ -219,6 +205,20 @@ class TestController extends Controller
$this->view->setVar('qrcode', $qrcode);
}
/**
* @Get("/alipay/status", name="admin.test.alipay_status")
*/
public function alipayStatusAction()
{
$sn = $this->request->getQuery('sn');
$alipayTestService = new AlipayTestService();
$status = $alipayTestService->status($sn);
return $this->jsonSuccess(['status' => $status]);
}
/**
* @Get("/wxpay/status", name="admin.test.wxpay_status")
*/

View File

@ -92,8 +92,8 @@ class ChapterContent extends Service
$attrs = $chapter->attrs;
$attrs['duration'] = 0;
$attrs['file_id'] = $fileId;
$attrs['file_status'] = ChapterModel::FS_UPLOADED;
$attrs['file']['status'] = ChapterModel::FS_UPLOADED;
$chapter->update(['attrs' => $attrs]);

View File

@ -92,7 +92,7 @@
layer.open({
type: 2,
title: '推流测试',
area: ['680px', '450px'],
area: ['720px', '500px'],
content: [url, 'no']
});
});

View File

@ -47,7 +47,7 @@
<span><a href="{{ edit_url }}">{{ item.title }}</a></span>
<span class="layui-badge layui-bg-green">课</span>
</td>
<td>{{ file_status(item.attrs['file_status']) }}</td>
<td>{{ file_status(item.attrs['file']['status']) }}</td>
<td>{{ item.attrs['duration']|duration }}</td>
<td><input class="layui-input kg-priority" type="text" name="priority" title="数值越小排序越靠前" value="{{ item.priority }}" data-url="{{ update_url }}"></td>
<td><input type="checkbox" name="free" value="1" lay-skin="switch" lay-text="是|否" lay-filter="free" data-url="{{ update_url }}" {% if item.free == 1 %}checked{% endif %}></td>

View File

@ -65,7 +65,7 @@
layer.open({
type: 2,
title: '推流测试',
area: ['680px', '450px'],
area: ['720px', '500px'],
content: [url, 'no']
});
});

View File

@ -2,6 +2,7 @@
namespace App\Http\Desktop\Controllers;
use App\Models\Course as CourseModel;
use App\Services\Frontend\Chapter\ChapterInfo as ChapterInfoService;
use App\Services\Frontend\Chapter\ChapterLike as ChapterLikeService;
use App\Services\Frontend\Chapter\DanmuList as ChapterDanmuListService;
@ -39,11 +40,11 @@ class ChapterController extends Controller
$this->seo->prependTitle(['章节', $chapter['title'], $chapter['course']['title']]);
$this->seo->setDescription($chapter['summary']);
if ($chapter['model'] == 'vod') {
if ($chapter['model'] == CourseModel::MODEL_VOD) {
$this->view->pick('chapter/vod');
} elseif ($chapter['model'] == 'live') {
} elseif ($chapter['model'] == CourseModel::MODEL_LIVE) {
$this->view->pick('chapter/live');
} elseif ($chapter['model'] == 'read') {
} elseif ($chapter['model'] == CourseModel::MODEL_READ) {
$this->view->pick('chapter/read');
}

View File

@ -51,9 +51,7 @@ class LiveController extends Controller
$stats = $service->getStats($id);
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('chapter/live_stats');
$this->view->setVar('stats', $stats);
return $this->jsonSuccess($stats);
}
/**

View File

@ -20,7 +20,7 @@
</span>
<span class="share">
<a href="javascript:" title="点赞" data-url="{{ like_url }}"><i class="layui-icon layui-icon-praise icon-praise"></i><em class="like-count">{{ chapter.like_count }}</em></a>
<a href="javascript:" title="在线人数"><i class="layui-icon layui-icon-user"></i><em>15</em></a>
<a href="javascript:" title="在线人数"><i class="layui-icon layui-icon-user"></i><em>0</em></a>
<a href="javascript:" title="分享到微信" data-url=""><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
<a href="javascript:" title="分享到QQ空间"><i class="layui-icon layui-icon-login-qq icon-qq"></i></a>
<a href="javascript:" title="分享到微博"><i class="layui-icon layui-icon-login-weibo icon-weibo"></i></a>
@ -56,6 +56,7 @@
<input type="hidden" name="chapter.plan_id" value="{{ chapter.me.plan_id }}">
<input type="hidden" name="chapter.learning_url" value="{{ learning_url }}">
<input type="hidden" name="chapter.play_urls" value='{{ chapter.play_urls|json_encode }}'>
<input type="hidden" name="live_stats_url" value='{{ live_stats_url }}'>
<input type="hidden" name="bind_user_url" value='{{ bind_user_url }}'>
</div>

View File

@ -9,7 +9,7 @@
</p>
{% elseif course.model == '2' %}
<p class="item">
<span class="key">直播时间</span><span>{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}</span>
<span class="key">直播时间</span><span class="value">{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}</span>
</p>
{% endif %}
{% if course.market_price > 0 %}

View File

@ -17,6 +17,13 @@ class Chapter extends Model
const FS_TRANSLATED = 'translated'; // 已转码
const FS_FAILED = 'failed'; // 已失败
/**
* 推流状态
*/
const SS_ACTIVE = 'active'; // 活跃
const SS_INACTIVE = 'inactive'; // 非活跃
const SS_FORBID = 'forbid'; // 禁播
/**
* @var array
*
@ -24,8 +31,7 @@ class Chapter extends Model
*/
protected $_vod_attrs = [
'duration' => 0,
'file_id' => '',
'file_status' => 'pending',
'file' => ['id' => '', 'status' => self::FS_PENDING],
];
/**
@ -36,6 +42,7 @@ class Chapter extends Model
protected $_live_attrs = [
'start_time' => 0,
'end_time' => 0,
'stream' => ['status' => self::SS_INACTIVE],
];
/**

View File

@ -5,6 +5,13 @@ namespace App\Models;
class ChapterLive extends Model
{
/**
* 状态类型
*/
const STATUS_ACTIVE = 1; // 活跃
const STATUS_INACTIVE = 2; // 非活跃
const STATUS_FORBID = 3; // 禁播
/**
* 主键编号
*
@ -47,6 +54,13 @@ class ChapterLive extends Model
*/
public $user_limit;
/**
* 直播状态
*
* @var int
*/
public $status;
/**
* 创建时间
*

View File

@ -67,7 +67,7 @@ trait BasicInfoTrait
$liveService = new LiveService();
$stream = $this->getLiveStreamName($chapter->id);
$stream = $this->getStreamName($chapter->id);
$format = $browserParser->isType('desktop') ? 'flv' : 'hls';

View File

@ -5,7 +5,7 @@ namespace App\Services\Frontend\Chapter;
trait ChapterLiveTrait
{
protected function getLiveStreamName($id)
protected function getStreamName($id)
{
return "chapter_{$id}";
}

View File

@ -21,7 +21,7 @@ class LivePushUrl extends FrontendService
$service = new LiveService();
$steamName = $this->getLiveStreamName($chapter->id);
$steamName = $this->getStreamName($chapter->id);
return $service->getPushUrl($steamName);
}

View File

@ -2,6 +2,10 @@
namespace App\Services;
use App\Models\Chapter as ChapterModel;
use App\Models\ChapterLive as ChapterLiveModel;
use App\Repos\Chapter as ChapterRepo;
class LiveNotify extends Service
{
@ -11,28 +15,27 @@ class LiveNotify extends Service
$sign = $this->request->getPost('sign');
$type = $this->request->getQuery('action');
if (!$this->checkSign($time, $sign)) {
if (!$this->checkSign($sign, $time)) {
return false;
}
$result = false;
switch ($type) {
case 'streamBegin':
$result = $this->streamBegin();
$result = $this->handleStreamBegin();
break;
case 'streamEnd':
$result = $this->streamEnd();
$result = $this->handleStreamEnd();
break;
case 'record':
$result = $this->record();
$result = $this->handleRecord();
break;
case 'snapshot':
$result = $this->snapshot();
$result = $this->handleSnapshot();
break;
case 'porn':
$result = $this->porn();
break;
default:
$result = false;
$result = $this->handlePorn();
break;
}
@ -42,45 +45,98 @@ class LiveNotify extends Service
/**
* 推流
*/
protected function streamBegin()
protected function handleStreamBegin()
{
$steamId = $this->request->getPost('stream_id');
$chapter = $this->getChapter($steamId);
if (!$chapter) return false;
$attrs = $chapter->attrs;
$attrs['stream']['status'] = ChapterModel::SS_ACTIVE;
$chapter->update(['attrs' => $attrs]);
$chapterLive = $this->getChapterLive($chapter->id);
$chapterLive->update(['status' => ChapterLiveModel::STATUS_ACTIVE]);
/**
* @todo 发送直播通知
*/
return true;
}
/**
* 断流
*/
protected function streamEnd()
protected function handleStreamEnd()
{
$steamId = $this->request->getPost('stream_id');
$chapter = $this->getChapter($steamId);
if (!$chapter) return false;
$attrs = $chapter->attrs;
$attrs['stream']['status'] = ChapterModel::SS_INACTIVE;
$chapter->update(['attrs' => $attrs]);
$chapterLive = $this->getChapterLive($chapter->id);
$chapterLive->update(['status' => ChapterLiveModel::STATUS_INACTIVE]);
return true;
}
/**
* 录制
*/
protected function handleRecord()
{
}
/**
* 断流
* 截图
*/
protected function record()
protected function handleSnapshot()
{
}
/**
* 断流
* 鉴黄
*/
protected function snapshot()
protected function handlePorn()
{
}
/**
* 断流
*/
protected function porn()
protected function getChapter($streamId)
{
$id = (int)str_replace('chapter_', '', $streamId);
$chapterRepo = new ChapterRepo();
return $chapterRepo->findById($id);
}
protected function getChapterLive($chapterId)
{
$chapterRepo = new ChapterRepo();
return $chapterRepo->findChapterLive($chapterId);
}
/**
* 检查签名
*
* @param string $sign
* @param int $time
* @return bool
@ -91,7 +147,7 @@ class LiveNotify extends Service
return false;
}
if ($time < time() + 600) {
if ($time < time()) {
return false;
}

View File

@ -2,7 +2,7 @@
use Phinx\Db\Adapter\MysqlAdapter;
class InitTable extends Phinx\Migration\AbstractMigration
class InitDb extends Phinx\Migration\AbstractMigration
{
public function change()
{
@ -643,19 +643,12 @@ class InitTable extends Phinx\Migration\AbstractMigration
'comment' => '章节编号',
'after' => 'course_id',
])
->addColumn('user_limit', 'integer', [
'null' => false,
'default' => '100',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '用户限额',
'after' => 'chapter_id',
])
->addColumn('start_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '开始时间',
'after' => 'user_limit',
'after' => 'chapter_id',
])
->addColumn('end_time', 'integer', [
'null' => false,
@ -664,12 +657,26 @@ class InitTable extends Phinx\Migration\AbstractMigration
'comment' => '结束时间',
'after' => 'start_time',
])
->addColumn('user_limit', 'integer', [
'null' => false,
'default' => '100',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '用户限额',
'after' => 'end_time',
])
->addColumn('status', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '状态标识',
'after' => 'user_limit',
])
->addColumn('create_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '创建时间',
'after' => 'end_time',
'after' => 'status',
])
->addColumn('update_time', 'integer', [
'null' => false,
@ -2251,7 +2258,7 @@ class InitTable extends Phinx\Migration\AbstractMigration
->addColumn('content', 'string', [
'null' => false,
'default' => '',
'limit' => 1500,
'limit' => 3000,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '内容',

View File

@ -2109,36 +2109,12 @@ return array(
'GENERATION_EXPRESSION' => '',
'SRS_ID' => NULL,
),
'user_limit' =>
array(
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'kg_chapter_live',
'COLUMN_NAME' => 'user_limit',
'ORDINAL_POSITION' => '4',
'COLUMN_DEFAULT' => '100',
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'int',
'CHARACTER_MAXIMUM_LENGTH' => NULL,
'CHARACTER_OCTET_LENGTH' => NULL,
'NUMERIC_PRECISION' => '10',
'NUMERIC_SCALE' => '0',
'DATETIME_PRECISION' => NULL,
'CHARACTER_SET_NAME' => NULL,
'COLLATION_NAME' => NULL,
'COLUMN_TYPE' => 'int unsigned',
'COLUMN_KEY' => '',
'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references',
'COLUMN_COMMENT' => '用户限额',
'GENERATION_EXPRESSION' => '',
'SRS_ID' => NULL,
),
'start_time' =>
array(
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'kg_chapter_live',
'COLUMN_NAME' => 'start_time',
'ORDINAL_POSITION' => '5',
'ORDINAL_POSITION' => '4',
'COLUMN_DEFAULT' => '0',
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'int',
@ -2162,7 +2138,7 @@ return array(
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'kg_chapter_live',
'COLUMN_NAME' => 'end_time',
'ORDINAL_POSITION' => '6',
'ORDINAL_POSITION' => '5',
'COLUMN_DEFAULT' => '0',
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'int',
@ -2181,12 +2157,60 @@ return array(
'GENERATION_EXPRESSION' => '',
'SRS_ID' => NULL,
),
'user_limit' =>
array(
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'kg_chapter_live',
'COLUMN_NAME' => 'user_limit',
'ORDINAL_POSITION' => '6',
'COLUMN_DEFAULT' => '100',
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'int',
'CHARACTER_MAXIMUM_LENGTH' => NULL,
'CHARACTER_OCTET_LENGTH' => NULL,
'NUMERIC_PRECISION' => '10',
'NUMERIC_SCALE' => '0',
'DATETIME_PRECISION' => NULL,
'CHARACTER_SET_NAME' => NULL,
'COLLATION_NAME' => NULL,
'COLUMN_TYPE' => 'int unsigned',
'COLUMN_KEY' => '',
'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references',
'COLUMN_COMMENT' => '用户限额',
'GENERATION_EXPRESSION' => '',
'SRS_ID' => NULL,
),
'status' =>
array(
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'kg_chapter_live',
'COLUMN_NAME' => 'status',
'ORDINAL_POSITION' => '7',
'COLUMN_DEFAULT' => '0',
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'int',
'CHARACTER_MAXIMUM_LENGTH' => NULL,
'CHARACTER_OCTET_LENGTH' => NULL,
'NUMERIC_PRECISION' => '10',
'NUMERIC_SCALE' => '0',
'DATETIME_PRECISION' => NULL,
'CHARACTER_SET_NAME' => NULL,
'COLLATION_NAME' => NULL,
'COLUMN_TYPE' => 'int unsigned',
'COLUMN_KEY' => '',
'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references',
'COLUMN_COMMENT' => '状态标识',
'GENERATION_EXPRESSION' => '',
'SRS_ID' => NULL,
),
'create_time' =>
array(
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'kg_chapter_live',
'COLUMN_NAME' => 'create_time',
'ORDINAL_POSITION' => '7',
'ORDINAL_POSITION' => '8',
'COLUMN_DEFAULT' => '0',
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'int',
@ -2210,7 +2234,7 @@ return array(
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'kg_chapter_live',
'COLUMN_NAME' => 'update_time',
'ORDINAL_POSITION' => '8',
'ORDINAL_POSITION' => '9',
'COLUMN_DEFAULT' => '0',
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'int',
@ -7877,14 +7901,14 @@ return array(
'COLUMN_DEFAULT' => '',
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'varchar',
'CHARACTER_MAXIMUM_LENGTH' => '1500',
'CHARACTER_OCTET_LENGTH' => '6000',
'CHARACTER_MAXIMUM_LENGTH' => '3000',
'CHARACTER_OCTET_LENGTH' => '12000',
'NUMERIC_PRECISION' => NULL,
'NUMERIC_SCALE' => NULL,
'DATETIME_PRECISION' => NULL,
'CHARACTER_SET_NAME' => 'utf8mb4',
'COLLATION_NAME' => 'utf8mb4_general_ci',
'COLUMN_TYPE' => 'varchar(1500)',
'COLUMN_TYPE' => 'varchar(3000)',
'COLUMN_KEY' => '',
'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references',

View File

@ -138,6 +138,10 @@ body {
background: none;
}
#header .layui-nav {
background: none;
}
#footer span, #footer a {
color: #999;
}

View File

@ -3,8 +3,9 @@ layui.use(['jquery', 'form', 'helper'], function () {
var $ = layui.jquery;
var form = layui.form;
var helper = layui.helper;
var socket = new WebSocket(window.koogua.socketUrl);
var socket = new WebSocket(window.im.websocket.url);
var bindUserUrl = $('input[name="bind_user_url"]').val();
var liveStatsUrl = $('input[name="live_stats_url"]').val();
var $chatContent = $('input[name=content]');
var $chatMsgList = $('#chat-msg-list');
@ -53,7 +54,7 @@ layui.use(['jquery', 'form', 'helper'], function () {
setInterval(function () {
refreshLiveStats();
}, 300000);
}, 30000);
function bindUser(clientId) {
$.ajax({
@ -92,8 +93,10 @@ layui.use(['jquery', 'form', 'helper'], function () {
}
function refreshLiveStats() {
var $tabStats = $('#tab-stats');
helper.ajaxLoadHtml($tabStats.data('url'), $tabStats.attr('id'));
var $count = $('.layui-icon-user').next();
$.get(liveStatsUrl, function (res) {
$count.text(res.client_count);
});
}
function loadRecentChats() {

View File

@ -5,7 +5,7 @@ layui.use(['jquery', 'helper'], function () {
var interval = null;
var intervalTime = 15000;
var userId = window.koogua.user.id;
var userId = window.user.id;
var chapterId = $('input[name="chapter.id"]').val();
var planId = $('input[name="chapter.plan_id"]').val();
var learningUrl = $('input[name="chapter.learning_url"]').val();