diff --git a/app/Console/Tasks/SyncLearningTask.php b/app/Console/Tasks/SyncLearningTask.php index 9a8a53f8..30c34efd 100644 --- a/app/Console/Tasks/SyncLearningTask.php +++ b/app/Console/Tasks/SyncLearningTask.php @@ -67,15 +67,21 @@ class SyncLearningTask extends Task $dbLearning = $learningRepo->findByRequestId($cacheLearning->request_id); if (!$dbLearning) { + $cacheLearning->create(); + + $this->updateChapterUser($cacheLearning); + } else { - $dbLearning->duration = $cacheLearning->duration; + + $dbLearning->duration += $cacheLearning->duration; $dbLearning->position = $cacheLearning->position; $dbLearning->active_time = $cacheLearning->active_time; - $dbLearning->update(); - } - $this->updateChapterUser($dbLearning); + $dbLearning->update(); + + $this->updateChapterUser($dbLearning); + } $this->cache->delete($itemKey); } @@ -87,7 +93,7 @@ class SyncLearningTask extends Task { $chapterUserRepo = new ChapterUserRepo(); - $chapterUser = $chapterUserRepo->findChapterUser($learning->chapter_id, $learning->user_id); + $chapterUser = $chapterUserRepo->findPlanChapterUser($learning->chapter_id, $learning->user_id, $learning->plan_id); if (!$chapterUser) return; @@ -130,44 +136,42 @@ class SyncLearningTask extends Task $chapterUser->update(); if ($chapterUser->consumed == 1) { - $this->updateCourseUser($chapterUser->course_id, $chapterUser->user_id); + $this->updateCourseUser($learning); } } /** - * @param int $courseId - * @param int $userId + * @param LearningModel $learning */ - protected function updateCourseUser($courseId, $userId) + protected function updateCourseUser(LearningModel $learning) { $courseUserRepo = new CourseUserRepo(); - $courseUser = $courseUserRepo->findCourseUser($courseId, $userId); + $courseUser = $courseUserRepo->findPlanCourseUser($learning->course_id, $learning->user_id, $learning->plan_id); if (!$courseUser) return; $courseRepo = new CourseRepo(); - $courseLessons = $courseRepo->findLessons($courseId); + $courseLessons = $courseRepo->findLessons($learning->course_id); if ($courseLessons->count() == 0) { return; } - $userLearnings = $courseRepo->findUserLearnings($courseId, $userId, $courseUser->plan_id); + $userLearnings = $courseRepo->findUserLearnings($learning->course_id, $learning->user_id, $learning->plan_id); if ($userLearnings->count() == 0) { return; } - /** - * @var array $consumedUserLearnings - */ - $consumedUserLearnings = $userLearnings->filter(function ($item) { - if ($item->consumed == 1) { - return $item; + $consumedUserLearnings = []; + + foreach ($userLearnings->toArray() as $userLearning) { + if ($userLearning['consumed'] == 1) { + $consumedUserLearnings[] = $userLearning; } - }); + } if (count($consumedUserLearnings) == 0) { return; @@ -175,8 +179,8 @@ class SyncLearningTask extends Task $duration = 0; - foreach ($consumedUserLearnings as $learning) { - $duration += $learning['duration']; + foreach ($consumedUserLearnings as $userLearning) { + $duration += $userLearning['duration']; } $courseLessonIds = kg_array_column($courseLessons->toArray(), 'id'); diff --git a/app/Http/Admin/Views/setting/vod.volt b/app/Http/Admin/Views/setting/vod.volt index 85080108..2f7ad8e3 100644 --- a/app/Http/Admin/Views/setting/vod.volt +++ b/app/Http/Admin/Views/setting/vod.volt @@ -157,16 +157,16 @@ var changeVideoTemplate = function (format) { var template = $('input[name=video_template]'); - if (format == 'mp4') { - template.val('10,20,30'); + if (format === 'mp4') { + template.val('100010,100020,100030'); } else { - template.val('210,220,230'); + template.val('100210,100220,100230'); } }; var changeAudioTemplate = function (format) { var template = $('input[name=audio_template]'); - if (format == 'mp3') { + if (format === 'mp3') { template.val('1010'); } else { template.val('1110'); @@ -175,7 +175,7 @@ form.on('radio(storage-type)', function (data) { var block = $('#storage-region-block'); - if (data.value == 'fixed') { + if (data.value === 'fixed') { block.show(); } else { block.hide(); @@ -184,7 +184,7 @@ form.on('radio(watermark-enabled)', function (data) { var block = $('#watermark-template-block'); - if (data.value == 1) { + if (data.value === 1) { block.show(); } else { block.hide(); @@ -193,7 +193,7 @@ form.on('radio(key-anti-enabled)', function (data) { var block = $('#key-anti-block'); - if (data.value == 1) { + if (data.value === 1) { block.show(); } else { block.hide(); diff --git a/app/Http/Admin/Views/student/learning.volt b/app/Http/Admin/Views/student/learning.volt index a2177503..05bd4bfe 100644 --- a/app/Http/Admin/Views/student/learning.volt +++ b/app/Http/Admin/Views/student/learning.volt @@ -1,4 +1,4 @@ -
{{ item.duration|play_duration }} | +{{ item.duration|total_duration }} | {{ date('Y-m-d H:i:s',item.active_time) }} | {% endfor %} diff --git a/app/Http/Web/Controllers/PublicController.php b/app/Http/Web/Controllers/PublicController.php index 6e061812..b02b14f6 100644 --- a/app/Http/Web/Controllers/PublicController.php +++ b/app/Http/Web/Controllers/PublicController.php @@ -3,6 +3,7 @@ namespace App\Http\Web\Controllers; use App\Models\ContentImage as ContentImageModel; +use App\Services\Frontend\Chapter\Learning as LearningService; use App\Services\Storage as StorageService; use App\Traits\Response as ResponseTrait; use PHPQRCode\QRcode as PHPQRCode; @@ -51,4 +52,16 @@ class PublicController extends \Phalcon\Mvc\Controller exit; } + /** + * @Post("/learning", name="web.learning") + */ + public function learningAction() + { + $service = new LearningService(); + + $service->handle(); + + return $this->jsonSuccess(); + } + } diff --git a/app/Http/Web/Views/chapter/show_vod.volt b/app/Http/Web/Views/chapter/show_vod.volt index a1c40968..204d2705 100644 --- a/app/Http/Web/Views/chapter/show_vod.volt +++ b/app/Http/Web/Views/chapter/show_vod.volt @@ -30,17 +30,40 @@ var interval = null; var intervalTime = 5000; - var requestId = getRequestId(); - var chapterId = '{{ chapter.id }}'; - var playUrl = 'https://1255691183.vod2.myqcloud.com/81258db0vodtransgzp1255691183/89b3d8955285890796532522693/v.f220.m3u8?t=5ee5c6ed&exper=0&us=697028&sign=2b7fd89eff92236184eadbaa14a895dd'; var position = 0; + var chapterId = '{{ chapter.id }}'; + var courseId = '{{ chapter.course.id }}'; + var planId = '{{ chapter.me.plan_id }}'; + var userId = '{{ auth_user.id }}'; + var requestId = getRequestId(); + var playUrls = JSON.parse('{{ chapter.play_urls|json_encode }}'); - var player = new TcPlayer('player', { - m3u8: playUrl, - autoplay: true, + var options = { + autoplay: false, width: 760, height: 450, - listener: function (msg) { + clarity: 'hd', + clarityLabel: { + od: '高清', + hd: '标清', + sd: '流畅' + } + }; + + if ('hd' in playUrls) { + options.m3u8 = playUrls.hd.url; + } + + if ('sd' in playUrls) { + options.m3u8_hd = playUrls.sd.url; + } + + if ('fd' in playUrls) { + options.m3u8_sd = playUrls.fd.url; + } + + if (userId !== '0') { + options.listener = function (msg) { if (msg.type === 'play') { start(); } else if (msg.type === 'pause') { @@ -49,7 +72,9 @@ stop(); } } - }); + } + + var player = new TcPlayer('player', options); if (position > 0) { player.currentTime(position); @@ -70,11 +95,14 @@ function learning() { $.ajax({ - type: 'GET', - url: '/admin/vod/learning', + type: 'POST', + url: '/learning', data: { request_id: requestId, chapter_id: chapterId, + course_id: courseId, + user_id: userId, + plan_id: planId, interval: intervalTime, position: player.currentTime(), } diff --git a/app/Repos/ChapterUser.php b/app/Repos/ChapterUser.php index 668c5a9c..5533b0ef 100644 --- a/app/Repos/ChapterUser.php +++ b/app/Repos/ChapterUser.php @@ -49,4 +49,18 @@ class ChapterUser extends Repository ]); } + /** + * @param int $chapterId + * @param int $userId + * @param int $planId + * @return ChapterUserModel|Model|bool + */ + public function findPlanChapterUser($chapterId, $userId, $planId) + { + return ChapterUserModel::findFirst([ + 'conditions' => 'chapter_id = ?1 AND user_id = ?2 AND plan_id = ?3 AND deleted = 0', + 'bind' => [1 => $chapterId, 2 => $userId, 3 => $planId], + ]); + } + } diff --git a/app/Repos/CourseUser.php b/app/Repos/CourseUser.php index 88c90f51..86059656 100644 --- a/app/Repos/CourseUser.php +++ b/app/Repos/CourseUser.php @@ -65,6 +65,20 @@ class CourseUser extends Repository return CourseUserModel::findFirst($id); } + /** + * @param int $courseId + * @param int $userId + * @param int $planId + * @return CourseUserModel|Model|bool + */ + public function findPlanCourseUser($courseId, $userId, $planId) + { + return CourseUserModel::findFirst([ + 'conditions' => 'course_id = ?1 AND user_id = ?2 AND plan_id = ?3 AND deleted = 0', + 'bind' => [1 => $courseId, 2 => $userId, 3 => $planId], + ]); + } + /** * @param int $courseId * @param int $userId diff --git a/app/Services/ChapterVod.php b/app/Services/ChapterVod.php index 6df399a0..708d0cef 100644 --- a/app/Services/ChapterVod.php +++ b/app/Services/ChapterVod.php @@ -24,11 +24,45 @@ class ChapterVod extends Service $vod = new Vod(); + $result = []; + foreach ($transcode as $key => $file) { - $transcode[$key]['url'] = $vod->getPlayUrl($file['url']); + + $file['url'] = $vod->getPlayUrl($file['url']); + + $definition = $this->getDefinitionType($file['height'], $file['rate']); + + $result[$definition] = $file; } - return $transcode; + return $result; + } + + protected function getDefinitionType($height, $rate) + { + $default = 'od'; + + $vodTemplates = $this->getVodTemplates(); + + foreach ($vodTemplates as $key => $template) { + if ($height >= $template['height'] || $rate >= $template['rate']) { + return $key; + } + } + + return $default; + } + + /** + * @return array + */ + protected function getVodTemplates() + { + return [ + 'hd' => ['height' => 720, 'rate' => 1800], + 'sd' => ['height' => 540, 'rate' => 1000], + 'fd' => ['height' => 360, 'rate' => 400], + ]; } } diff --git a/app/Services/Frontend/Chapter/ChapterInfo.php b/app/Services/Frontend/Chapter/ChapterInfo.php index d7057ab8..121e84cd 100644 --- a/app/Services/Frontend/Chapter/ChapterInfo.php +++ b/app/Services/Frontend/Chapter/ChapterInfo.php @@ -45,9 +45,9 @@ class ChapterInfo extends FrontendService $this->user = $user; $this->setCourseUser($course, $user); - $this->setChapterUser($chapter, $user); - $this->handleCourseUser($course, $user); + + $this->setChapterUser($chapter, $user); $this->handleChapterUser($chapter, $user); return $this->handleChapter($chapter, $user); @@ -60,11 +60,19 @@ class ChapterInfo extends FrontendService $result['course'] = $this->handleCourse($this->course); $me = [ + 'plan_id' => 0, + 'joined' => 0, + 'owned' => 0, 'agreed' => 0, 'opposed' => 0, ]; - $me['owned'] = $this->ownedChapter; + if ($this->courseUser) { + $me['plan_id'] = $this->courseUser->plan_id; + } + + $me['joined'] = $this->joinedChapter ? 1 : 0; + $me['owned'] = $this->ownedChapter ? 1 : 0; if ($user->id > 0) { @@ -217,9 +225,12 @@ class ChapterInfo extends FrontendService $courseUser->user_id = $user->id; $courseUser->source_type = CourseUserModel::SOURCE_FREE; $courseUser->role_type = CourseUserModel::ROLE_STUDENT; + $courseUser->expiry_time = strtotime('+3 years'); $courseUser->create(); + $this->courseUser = $courseUser; + $this->incrCourseUserCount($course); } @@ -227,14 +238,7 @@ class ChapterInfo extends FrontendService { if ($user->id == 0) return; - /** - * 一个课程可能购买学习多次 - */ - if ($this->chapterUser && $this->courseUser) { - if ($this->chapterUser->plan_id == $this->courseUser->plan_id) { - return; - } - } + if ($this->joinedChapter) return; if (!$this->ownedChapter) return; @@ -247,6 +251,8 @@ class ChapterInfo extends FrontendService $chapterUser->create(); + $this->chapterUser = $chapterUser; + $this->incrChapterUserCount($chapter); } diff --git a/app/Services/Frontend/Chapter/Learning.php b/app/Services/Frontend/Chapter/Learning.php index 50e8fb94..d835a1d2 100644 --- a/app/Services/Frontend/Chapter/Learning.php +++ b/app/Services/Frontend/Chapter/Learning.php @@ -14,24 +14,24 @@ class Learning extends FrontendService use ChapterTrait; - public function handle($id) + public function handle() { - $chapter = $this->checkChapterCache($id); - - $user = $this->getCurrentUser(); - $post = $this->request->getPost(); - if ($user->id == 0) return; + $chapter = $this->checkChapterCache($post['chapter_id']); + + $user = $this->getLoginUser(); $validator = new LearningValidator(); $data = [ + 'course_id' => $chapter->course_id, 'chapter_id' => $chapter->id, 'user_id' => $user->id, ]; $data['request_id'] = $validator->checkRequestId($post['request_id']); + $data['plan_id'] = $validator->checkPlanId($post['plan_id']); /** * @var array $attrs diff --git a/app/Services/Frontend/ChapterTrait.php b/app/Services/Frontend/ChapterTrait.php index 864b7c8b..c632ecc1 100644 --- a/app/Services/Frontend/ChapterTrait.php +++ b/app/Services/Frontend/ChapterTrait.php @@ -22,7 +22,7 @@ trait ChapterTrait protected $joinedChapter = false; /** - * @var ChapterUserModel + * @var ChapterUserModel|null */ protected $chapterUser; @@ -42,12 +42,18 @@ trait ChapterTrait public function setChapterUser(ChapterModel $chapter, UserModel $user) { - $chapterUserRepo = new ChapterUserRepo(); + $chapterUser = null; - $chapterUser = $chapterUserRepo->findChapterUser($chapter->id, $user->id); + $courseUser = $this->courseUser; - if ($chapterUser) { - $this->chapterUser = $chapterUser; + if ($user->id > 0 && $courseUser) { + $chapterUserRepo = new ChapterUserRepo(); + $chapterUser = $chapterUserRepo->findChapterUser($chapter->id, $user->id); + } + + $this->chapterUser = $chapterUser; + + if ($chapterUser && $chapterUser->plan_id == $courseUser->plan_id) { $this->joinedChapter = true; } diff --git a/app/Services/Frontend/Course/CourseInfo.php b/app/Services/Frontend/Course/CourseInfo.php index 290e8336..83b00738 100644 --- a/app/Services/Frontend/Course/CourseInfo.php +++ b/app/Services/Frontend/Course/CourseInfo.php @@ -53,6 +53,7 @@ class CourseInfo extends FrontendService ]; $me = [ + 'plan_id' => 0, 'joined' => 0, 'owned' => 0, 'reviewed' => 0, @@ -60,6 +61,9 @@ class CourseInfo extends FrontendService 'progress' => 0, ]; + $me['joined'] = $this->joinedCourse ? 1 : 0; + $me['owned'] = $this->ownedCourse ? 1 : 0; + if ($user->id > 0) { $favoriteRepo = new CourseFavoriteRepo(); @@ -73,10 +77,8 @@ class CourseInfo extends FrontendService if ($this->courseUser) { $me['reviewed'] = $this->courseUser->reviewed ? 1 : 0; $me['progress'] = $this->courseUser->progress ? 1 : 0; + $me['plan_id'] = $this->courseUser->plan_id; } - - $me['joined'] = $this->joinedCourse ? 1 : 0; - $me['owned'] = $this->ownedCourse ? 1 : 0; } $result['me'] = $me; diff --git a/app/Services/Syncer/Learning.php b/app/Services/Syncer/Learning.php index c7fe512a..3dff24d6 100644 --- a/app/Services/Syncer/Learning.php +++ b/app/Services/Syncer/Learning.php @@ -4,7 +4,6 @@ namespace App\Services\Syncer; use App\Library\Cache\Backend\Redis as RedisCache; use App\Models\Learning as LearningModel; -use App\Repos\Chapter as ChapterRepo; use App\Services\Service; use App\Traits\Client as ClientTrait; @@ -50,11 +49,6 @@ class Learning extends Service if (!$cacheLearning) { - $chapterRepo = new ChapterRepo(); - - $chapter = $chapterRepo->findById($learning->chapter_id); - - $learning->course_id = $chapter->course_id; $learning->client_type = $this->getClientType(); $learning->client_ip = $this->getClientIp(); $learning->duration = $interval; diff --git a/app/Services/Vod.php b/app/Services/Vod.php index d4f653de..a9c456e7 100644 --- a/app/Services/Vod.php +++ b/app/Services/Vod.php @@ -380,7 +380,7 @@ class Vod extends Service foreach ($videoTransTemplates as $key => $template) { - $caseA = $originVideoInfo['width'] >= $template['width']; + $caseA = $originVideoInfo['height'] >= $template['height']; $caseB = $originVideoInfo['bit_rate'] >= 1000 * $template['bit_rate']; if ($caseA || $caseB) { @@ -589,15 +589,15 @@ class Vod extends Service public function getVideoTransTemplates() { $hls = [ - 210 => ['width' => 480, 'bit_rate' => 256, 'frame_rate' => 24], - 220 => ['width' => 640, 'bit_rate' => 512, 'frame_rate' => 24], - 230 => ['width' => 1280, 'bit_rate' => 1024, 'frame_rate' => 25], + 100210 => ['height' => 360, 'bit_rate' => 400, 'frame_rate' => 25], + 100220 => ['height' => 540, 'bit_rate' => 1000, 'frame_rate' => 25], + 100230 => ['height' => 720, 'bit_rate' => 1800, 'frame_rate' => 25], ]; $mp4 = [ - 10 => ['width' => 480, 'bit_rate' => 256, 'frame_rate' => 24], - 20 => ['width' => 640, 'bit_rate' => 512, 'frame_rate' => 24], - 30 => ['width' => 1280, 'bit_rate' => 1024, 'frame_rate' => 25], + 100010 => ['height' => 360, 'bit_rate' => 400, 'frame_rate' => 25], + 100020 => ['height' => 540, 'bit_rate' => 1000, 'frame_rate' => 25], + 100030 => ['height' => 720, 'bit_rate' => 1800, 'frame_rate' => 25], ]; $format = $this->settings['video_format']; diff --git a/app/Validators/Learning.php b/app/Validators/Learning.php index c35e84bc..97365394 100644 --- a/app/Validators/Learning.php +++ b/app/Validators/Learning.php @@ -3,22 +3,18 @@ namespace App\Validators; use App\Exceptions\BadRequest as BadRequestException; -use App\Repos\Chapter as ChapterRepo; +use App\Library\Validators\Common as CommonValidator; class Learning extends Validator { - public function checkChapterId($chapterId) + public function checkPlanId($planId) { - $chapterRepo = new ChapterRepo(); - - $chapter = $chapterRepo->findById($chapterId); - - if (!$chapter) { - throw new BadRequestException('learning.invalid_chapter_id'); + if (!CommonValidator::date($planId, 'Ymd')) { + throw new BadRequestException('learning.invalid_plan_id'); } - return $chapterId; + return $planId; } public function checkRequestId($requestId) @@ -50,7 +46,7 @@ class Learning extends Validator public function checkPosition($position) { - $value = $this->filter->sanitize($position, ['trim', 'int']); + $value = $this->filter->sanitize($position, ['trim', 'float']); if ($value < 0 || $value > 3 * 3600) { throw new BadRequestException('learning.invalid_position'); diff --git a/config/errors.php b/config/errors.php index 864618eb..89f6849c 100644 --- a/config/errors.php +++ b/config/errors.php @@ -320,7 +320,7 @@ $error['course_query.invalid_sort'] = '无效的排序类别'; /** * 课时学习 */ -$error['learning.invalid_chapter_id'] = '无效的章节编号'; +$error['learning.invalid_plan_id'] = '无效的计划编号'; $error['learning.invalid_request_id'] = '无效的请求编号'; $error['learning.invalid_position'] = '无效的播放位置'; $error['learning.invalid_interval'] = '无效的间隔时间'; diff --git a/public/static/web/css/common.css b/public/static/web/css/common.css index 2e0f894d..86867322 100644 --- a/public/static/web/css/common.css +++ b/public/static/web/css/common.css @@ -363,7 +363,6 @@ position: relative; float: left; width: 80%; - font-size: 12px; } .course-meta .share {