diff --git a/app/Console/Tasks/VodEventTask.php b/app/Console/Tasks/VodEventTask.php index 8c235f38..07795c5b 100644 --- a/app/Console/Tasks/VodEventTask.php +++ b/app/Console/Tasks/VodEventTask.php @@ -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]); } diff --git a/app/Http/Admin/Controllers/TestController.php b/app/Http/Admin/Controllers/TestController.php index 03bc8d7b..fa017431 100644 --- a/app/Http/Admin/Controllers/TestController.php +++ b/app/Http/Admin/Controllers/TestController.php @@ -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") */ diff --git a/app/Http/Admin/Services/ChapterContent.php b/app/Http/Admin/Services/ChapterContent.php index a019d734..1fbc24c6 100644 --- a/app/Http/Admin/Services/ChapterContent.php +++ b/app/Http/Admin/Services/ChapterContent.php @@ -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]); diff --git a/app/Http/Admin/Views/chapter/edit_lesson.volt b/app/Http/Admin/Views/chapter/edit_lesson.volt index c75920da..9b30eba6 100644 --- a/app/Http/Admin/Views/chapter/edit_lesson.volt +++ b/app/Http/Admin/Views/chapter/edit_lesson.volt @@ -92,7 +92,7 @@ layer.open({ type: 2, title: '推流测试', - area: ['680px', '450px'], + area: ['720px', '500px'], content: [url, 'no'] }); }); diff --git a/app/Http/Admin/Views/chapter/lessons_vod.volt b/app/Http/Admin/Views/chapter/lessons_vod.volt index 6221f7ca..c1769752 100644 --- a/app/Http/Admin/Views/chapter/lessons_vod.volt +++ b/app/Http/Admin/Views/chapter/lessons_vod.volt @@ -47,7 +47,7 @@ {{ item.title }} - {{ file_status(item.attrs['file_status']) }} + {{ file_status(item.attrs['file']['status']) }} {{ item.attrs['duration']|duration }} diff --git a/app/Http/Admin/Views/setting/live.volt b/app/Http/Admin/Views/setting/live.volt index 1aa8f67e..7947eddd 100644 --- a/app/Http/Admin/Views/setting/live.volt +++ b/app/Http/Admin/Views/setting/live.volt @@ -65,7 +65,7 @@ layer.open({ type: 2, title: '推流测试', - area: ['680px', '450px'], + area: ['720px', '500px'], content: [url, 'no'] }); }); diff --git a/app/Http/Desktop/Controllers/ChapterController.php b/app/Http/Desktop/Controllers/ChapterController.php index da06c2d1..b575881a 100644 --- a/app/Http/Desktop/Controllers/ChapterController.php +++ b/app/Http/Desktop/Controllers/ChapterController.php @@ -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'); } diff --git a/app/Http/Desktop/Controllers/LiveController.php b/app/Http/Desktop/Controllers/LiveController.php index 835591ae..dbfb011c 100644 --- a/app/Http/Desktop/Controllers/LiveController.php +++ b/app/Http/Desktop/Controllers/LiveController.php @@ -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); } /** diff --git a/app/Http/Desktop/Views/chapter/live.volt b/app/Http/Desktop/Views/chapter/live.volt index 7d9851a4..30403179 100644 --- a/app/Http/Desktop/Views/chapter/live.volt +++ b/app/Http/Desktop/Views/chapter/live.volt @@ -20,7 +20,7 @@ - 15 + 0 @@ -56,6 +56,7 @@ + diff --git a/app/Http/Desktop/Views/course/show_meta.volt b/app/Http/Desktop/Views/course/show_meta.volt index d83435a8..e33e996d 100644 --- a/app/Http/Desktop/Views/course/show_meta.volt +++ b/app/Http/Desktop/Views/course/show_meta.volt @@ -9,7 +9,7 @@

{% elseif course.model == '2' %}

- 直播时间{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }} + 直播时间{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}

{% endif %} {% if course.market_price > 0 %} diff --git a/app/Models/Chapter.php b/app/Models/Chapter.php index e6f4fdb7..ca974deb 100644 --- a/app/Models/Chapter.php +++ b/app/Models/Chapter.php @@ -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], ]; /** diff --git a/app/Models/ChapterLive.php b/app/Models/ChapterLive.php index 75def952..50823c39 100644 --- a/app/Models/ChapterLive.php +++ b/app/Models/ChapterLive.php @@ -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; + /** * 创建时间 * diff --git a/app/Services/Frontend/Chapter/BasicInfoTrait.php b/app/Services/Frontend/Chapter/BasicInfoTrait.php index fa16b871..803ccfd7 100644 --- a/app/Services/Frontend/Chapter/BasicInfoTrait.php +++ b/app/Services/Frontend/Chapter/BasicInfoTrait.php @@ -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'; diff --git a/app/Services/Frontend/Chapter/ChapterLiveTrait.php b/app/Services/Frontend/Chapter/ChapterLiveTrait.php index 336d95b1..af6a2e32 100644 --- a/app/Services/Frontend/Chapter/ChapterLiveTrait.php +++ b/app/Services/Frontend/Chapter/ChapterLiveTrait.php @@ -5,7 +5,7 @@ namespace App\Services\Frontend\Chapter; trait ChapterLiveTrait { - protected function getLiveStreamName($id) + protected function getStreamName($id) { return "chapter_{$id}"; } diff --git a/app/Services/Frontend/Teaching/LivePushUrl.php b/app/Services/Frontend/Teaching/LivePushUrl.php index 813f4d1e..a37d63be 100644 --- a/app/Services/Frontend/Teaching/LivePushUrl.php +++ b/app/Services/Frontend/Teaching/LivePushUrl.php @@ -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); } diff --git a/app/Services/LiveNotify.php b/app/Services/LiveNotify.php index 241321f5..774ab59c 100644 --- a/app/Services/LiveNotify.php +++ b/app/Services/LiveNotify.php @@ -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; } diff --git a/db/migrations/20200827063842_init_table.php b/db/migrations/20200827063842_init_table.php index 37e42d7d..915d0dab 100644 --- a/db/migrations/20200827063842_init_table.php +++ b/db/migrations/20200827063842_init_table.php @@ -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' => '内容', diff --git a/db/migrations/schema.php b/db/migrations/schema.php index 3140eeb5..2a81018d 100644 --- a/db/migrations/schema.php +++ b/db/migrations/schema.php @@ -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', diff --git a/public/static/desktop/css/common.css b/public/static/desktop/css/common.css index 78569189..eb2de66b 100644 --- a/public/static/desktop/css/common.css +++ b/public/static/desktop/css/common.css @@ -138,6 +138,10 @@ body { background: none; } +#header .layui-nav { + background: none; +} + #footer span, #footer a { color: #999; } diff --git a/public/static/desktop/js/chapter.live.im.js b/public/static/desktop/js/chapter.live.im.js index 5555799c..834975cc 100644 --- a/public/static/desktop/js/chapter.live.im.js +++ b/public/static/desktop/js/chapter.live.im.js @@ -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() { diff --git a/public/static/desktop/js/chapter.live.player.js b/public/static/desktop/js/chapter.live.player.js index ace16d79..3425ae35 100644 --- a/public/static/desktop/js/chapter.live.player.js +++ b/public/static/desktop/js/chapter.live.player.js @@ -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();