diff --git a/app/Builders/DanmuList.php b/app/Builders/DanmuList.php
new file mode 100644
index 00000000..270514b4
--- /dev/null
+++ b/app/Builders/DanmuList.php
@@ -0,0 +1,99 @@
+getCourses($danmus);
+
+ foreach ($danmus as $key => $danmu) {
+ $danmus[$key]['course'] = $courses[$danmu['course_id']] ?? new \stdClass();
+ }
+
+ return $danmus;
+ }
+
+ public function handleChapters(array $danmus)
+ {
+ $chapters = $this->getChapters($danmus);
+
+ foreach ($danmus as $key => $danmu) {
+ $danmus[$key]['chapter'] = $chapters[$danmu['chapter_id']] ?? new \stdClass();
+ }
+
+ return $danmus;
+ }
+
+ public function handleUsers(array $danmus)
+ {
+ $users = $this->getUsers($danmus);
+
+ foreach ($danmus as $key => $danmu) {
+ $danmus[$key]['user'] = $users[$danmu['user_id']] ?? new \stdClass();
+ }
+
+ return $danmus;
+ }
+
+ public function getCourses(array $danmus)
+ {
+ $ids = kg_array_column($danmus, 'course_id');
+
+ $courseRepo = new CourseRepo();
+
+ $courses = $courseRepo->findByIds($ids, ['id', 'title']);
+
+ $result = [];
+
+ foreach ($courses->toArray() as $course) {
+ $result[$course['id']] = $course;
+ }
+
+ return $result;
+ }
+
+ public function getChapters(array $danmus)
+ {
+ $ids = kg_array_column($danmus, 'chapter_id');
+
+ $chapterRepo = new ChapterRepo();
+
+ $chapters = $chapterRepo->findByIds($ids, ['id', 'title']);
+
+ $result = [];
+
+ foreach ($chapters->toArray() as $chapter) {
+ $result[$chapter['id']] = $chapter;
+ }
+
+ return $result;
+ }
+
+ public function getUsers(array $danmus)
+ {
+ $ids = kg_array_column($danmus, 'user_id');
+
+ $userRepo = new UserRepo();
+
+ $users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
+
+ $baseUrl = kg_ci_base_url();
+
+ $result = [];
+
+ foreach ($users->toArray() as $user) {
+ $user['avatar'] = $baseUrl . $user['avatar'];
+ $result[$user['id']] = $user;
+ }
+
+ return $result;
+ }
+
+}
diff --git a/app/Http/Web/Controllers/ChapterController.php b/app/Http/Web/Controllers/ChapterController.php
index 30e405bb..8b3c4393 100644
--- a/app/Http/Web/Controllers/ChapterController.php
+++ b/app/Http/Web/Controllers/ChapterController.php
@@ -5,6 +5,7 @@ namespace App\Http\Web\Controllers;
use App\Services\Frontend\Chapter\AgreeVote as ChapterAgreeVoteService;
use App\Services\Frontend\Chapter\ChapterInfo as ChapterInfoService;
use App\Services\Frontend\Chapter\CommentList as ChapterCommentListService;
+use App\Services\Frontend\Chapter\DanmuList as ChapterDanmuListService;
use App\Services\Frontend\Chapter\Learning as ChapterLearningService;
use App\Services\Frontend\Chapter\OpposeVote as ChapterOpposeVoteService;
use App\Services\Frontend\Course\ChapterList as CourseChapterListService;
@@ -53,6 +54,18 @@ class ChapterController extends Controller
$this->view->setVar('chapters', $chapters);
}
+ /**
+ * @Get("/{id:[0-9]+}/danmu", name="web.chapter.danmu")
+ */
+ public function danmuAction($id)
+ {
+ $service = new ChapterDanmuListService();
+
+ $items = $service->handle($id);
+
+ return $this->jsonSuccess(['items' => $items]);
+ }
+
/**
* @Get("/{id:[0-9]+}/comments", name="web.chapter.comments")
*/
diff --git a/app/Http/Web/Views/chapter/show_vod.volt b/app/Http/Web/Views/chapter/show_vod.volt
index 04454ddd..62de09ae 100644
--- a/app/Http/Web/Views/chapter/show_vod.volt
+++ b/app/Http/Web/Views/chapter/show_vod.volt
@@ -3,6 +3,7 @@
{% block content %}
{% set learning_url = url({'for':'web.chapter.learning','id':chapter.id}) %}
+ {% set danmu_url = url({'for':'web.chapter.danmu','id':chapter.id}) %}
@@ -20,15 +21,15 @@
@@ -40,8 +41,10 @@
+
+
diff --git a/app/Models/Danmu.php b/app/Models/Danmu.php
index 607b13c5..fc6155db 100644
--- a/app/Models/Danmu.php
+++ b/app/Models/Danmu.php
@@ -16,9 +16,18 @@ class Danmu extends Model
/**
* 位置类型
*/
- const POS_MOVE = 0; // 滚动
- const POS_TOP = 1; // 顶部
- const POS_BOTTOM = 2; // 底部
+ const POSITION_MOVE = 0; // 滚动
+ const POSITION_TOP = 1; // 顶部
+ const POSITION_BOTTOM = 2; // 底部
+
+ /**
+ * 颜色类型
+ */
+ const COLOR_WHITE = 'white'; // 白色
+ const COLOR_RED = 'red'; // 红色
+ const COLOR_BLUE = 'blue'; // 蓝色
+ const COLOR_GREEN = 'green'; // 绿色
+ const COLOR_YELLOW = 'yellow'; // 黄色
/**
* 主键编号
@@ -141,17 +150,51 @@ class Danmu extends Model
public static function sizeTypes()
{
return [
- '0' => '小号',
- '1' => '大号',
+ self::SIZE_SMALL => '小号',
+ self::SIZE_BIG => '大号',
];
}
- public static function positionTypes()
+ public static function posTypes()
{
return [
- '0' => '滚动',
- '1' => '顶部',
- '2' => '底部',
+ self::POSITION_MOVE => '滚动',
+ self::POSITION_TOP => '顶部',
+ self::POSITION_BOTTOM => '底部',
];
}
+
+ public static function colorTypes()
+ {
+ return [
+ self::COLOR_WHITE => '白色',
+ self::COLOR_RED => '红色',
+ self::COLOR_GREEN => '绿色',
+ self::COLOR_BLUE => '蓝色',
+ self::COLOR_YELLOW => '黄色',
+ ];
+ }
+
+ public static function randPos()
+ {
+ $types = self::positionTypes();
+
+ $keys = array_keys($types);
+
+ $index = array_rand($keys);
+
+ return $keys[$index];
+ }
+
+ public static function randColor()
+ {
+ $types = self::colorTypes();
+
+ $keys = array_keys($types);
+
+ $index = array_rand($keys);
+
+ return $keys[$index];
+ }
+
}
diff --git a/app/Repos/Danmu.php b/app/Repos/Danmu.php
index 6c8e2809..da807ae7 100644
--- a/app/Repos/Danmu.php
+++ b/app/Repos/Danmu.php
@@ -82,4 +82,43 @@ class Danmu extends Repository
->execute();
}
+ /**
+ * @param array $where
+ * @return ResultsetInterface|Resultset|DanmuModel[]
+ */
+ public function findAll($where = [])
+ {
+ $query = DanmuModel::query();
+
+ $query->where('1 = 1');
+
+ if (!empty($where['course_id'])) {
+ $query->andWhere('course_id = :course_id:', ['course_id' => $where['course_id']]);
+ }
+
+ if (!empty($where['chapter_id'])) {
+ $query->andWhere('chapter_id = :chapter_id:', ['chapter_id' => $where['chapter_id']]);
+ }
+
+ if (!empty($where['user_id'])) {
+ $query->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
+ }
+
+ if (!empty($where['start_time']) && !empty($where['end_time'])) {
+ $query->betweenWhere('time', $where['start_time'], $where['end_time']);
+ }
+
+ if (isset($where['published'])) {
+ $query->andWhere('published = :published:', ['published' => $where['published']]);
+ }
+
+ if (isset($where['deleted'])) {
+ $query->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
+ }
+
+ $query->orderBy('id DESC');
+
+ return $query->execute();
+ }
+
}
diff --git a/app/Services/Frontend/Chapter/ChapterInfo.php b/app/Services/Frontend/Chapter/ChapterInfo.php
index e0fe0601..34696c93 100644
--- a/app/Services/Frontend/Chapter/ChapterInfo.php
+++ b/app/Services/Frontend/Chapter/ChapterInfo.php
@@ -60,6 +60,7 @@ class ChapterInfo extends FrontendService
$me = [
'plan_id' => 0,
+ 'position' => 0,
'joined' => 0,
'owned' => 0,
'agreed' => 0,
@@ -70,6 +71,10 @@ class ChapterInfo extends FrontendService
$me['plan_id'] = $this->courseUser->plan_id;
}
+ if ($this->chapterUser) {
+ $me['position'] = $this->chapterUser->position;
+ }
+
$me['joined'] = $this->joinedChapter ? 1 : 0;
$me['owned'] = $this->ownedChapter ? 1 : 0;
@@ -253,6 +258,16 @@ class ChapterInfo extends FrontendService
$this->incrChapterUserCount($chapter);
}
+ protected function getVodPosition(ChapterModel $chapter, UserModel $user)
+ {
+
+ }
+
+ protected function getLiveStreamName($id)
+ {
+ return "chapter_{$id}";
+ }
+
protected function incrCourseUserCount(CourseModel $course)
{
$this->eventsManager->fire('courseCounter:incrUserCount', $this, $course);
@@ -263,9 +278,4 @@ class ChapterInfo extends FrontendService
$this->eventsManager->fire('chapterCounter:incrUserCount', $this, $chapter);
}
- protected function getLiveStreamName($id)
- {
- return "chapter_{$id}";
- }
-
}
diff --git a/app/Services/Frontend/Chapter/DanmuList.php b/app/Services/Frontend/Chapter/DanmuList.php
new file mode 100644
index 00000000..1db6cc30
--- /dev/null
+++ b/app/Services/Frontend/Chapter/DanmuList.php
@@ -0,0 +1,67 @@
+checkChapter($id);
+
+ $params = [];
+
+ $params['chapter_id'] = $chapter->id;
+ $params['published'] = 1;
+
+ $danmuRepo = new DanmuRepo();
+
+ $items = $danmuRepo->findAll($params);
+
+ $result = [];
+
+ if ($items->count() > 0) {
+ $result = $this->handleItems($items->toArray());
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param array $items
+ * @return array
+ */
+ protected function handleItems($items)
+ {
+ $builder = new DanmuListBuilder();
+
+ $users = $builder->getUsers($items);
+
+ $result = [];
+
+ foreach ($items as $item) {
+
+ $user = $users[$item['user_id']] ?? new \stdClass();
+
+ $result[] = [
+ 'id' => $item['id'],
+ 'text' => $item['text'],
+ 'color' => $item['color'],
+ 'size' => $item['size'],
+ 'time' => $item['time'],
+ 'position' => $item['position'],
+ 'user' => $user,
+ ];
+ }
+
+ return $result;
+ }
+
+}
diff --git a/app/Services/Frontend/Danmu/DanmuCreate.php b/app/Services/Frontend/Danmu/DanmuCreate.php
index 7ea302a6..e4d431f3 100644
--- a/app/Services/Frontend/Danmu/DanmuCreate.php
+++ b/app/Services/Frontend/Danmu/DanmuCreate.php
@@ -38,7 +38,9 @@ class DanmuCreate extends FrontendService
$data['course_id'] = $chapter->course_id;
$data['chapter_id'] = $chapter->id;
$data['user_id'] = $user->id;
- $data['color'] = 'white';
+ $data['position'] = DanmuModel::POSITION_MOVE;
+ $data['color'] = DanmuModel::COLOR_WHITE;
+ $data['size'] = DanmuModel::SIZE_SMALL;
$data['published'] = 1;
$danmu->create($data);
diff --git a/app/Services/Frontend/Help/HelpList.php b/app/Services/Frontend/Help/HelpList.php
index 8531189a..371fe255 100644
--- a/app/Services/Frontend/Help/HelpList.php
+++ b/app/Services/Frontend/Help/HelpList.php
@@ -17,9 +17,13 @@ class HelpList extends FrontendService
$helps = $helpRepo->findAll($params);
+ $result = [];
+
if ($helps->count() > 0) {
- return $this->handleHelps($helps);
+ $result = $this->handleHelps($helps);
}
+
+ return $result;
}
/**
@@ -31,7 +35,6 @@ class HelpList extends FrontendService
$items = [];
foreach ($helps as $help) {
-
$items[] = [
'id' => $help->id,
'title' => $help->title,
diff --git a/public/static/web/js/live.player.js b/public/static/web/js/live.player.js
index 29ae0ed4..ace16d79 100644
--- a/public/static/web/js/live.player.js
+++ b/public/static/web/js/live.player.js
@@ -5,7 +5,6 @@ layui.use(['jquery', 'helper'], function () {
var interval = null;
var intervalTime = 15000;
- var position = 0;
var userId = window.koogua.user.id;
var chapterId = $('input[name="chapter.id"]').val();
var planId = $('input[name="chapter.plan_id"]').val();
@@ -57,24 +56,18 @@ layui.use(['jquery', 'helper'], function () {
options.m3u8_sd = playUrls.m3u8.sd;
}
- if (userId !== '0' && planId !== '0') {
- options.listener = function (msg) {
- if (msg.type === 'play') {
- start();
- } else if (msg.type === 'pause') {
- stop();
- } else if (msg.type === 'end') {
- stop();
- }
+ options.listener = function (msg) {
+ if (msg.type === 'play') {
+ start();
+ } else if (msg.type === 'pause') {
+ stop();
+ } else if (msg.type === 'end') {
+ stop();
}
- }
+ };
var player = new TcPlayer('player', options);
- if (position > 0) {
- player.currentTime(position);
- }
-
function start() {
if (interval != null) {
clearInterval(interval);
@@ -84,22 +77,26 @@ layui.use(['jquery', 'helper'], function () {
}
function stop() {
- clearInterval(interval);
- interval = null;
+ if (interval != null) {
+ clearInterval(interval);
+ interval = null;
+ }
}
function learning() {
- $.ajax({
- type: 'POST',
- url: learningUrl,
- data: {
- request_id: requestId,
- chapter_id: chapterId,
- plan_id: planId,
- interval: intervalTime,
- position: player.currentTime(),
- }
- });
+ if (userId !== '0' && planId !== '0') {
+ $.ajax({
+ type: 'POST',
+ url: learningUrl,
+ data: {
+ plan_id: planId,
+ chapter_id: chapterId,
+ request_id: requestId,
+ interval: intervalTime,
+ position: player.currentTime(),
+ }
+ });
+ }
}
});
\ No newline at end of file
diff --git a/public/static/web/js/vod.player.js b/public/static/web/js/vod.player.js
index 5e501bcc..6df618fd 100644
--- a/public/static/web/js/vod.player.js
+++ b/public/static/web/js/vod.player.js
@@ -1,18 +1,21 @@
-layui.use(['jquery', 'form', 'helper'], function () {
+layui.use(['jquery', 'form', 'layer', 'helper'], function () {
var $ = layui.jquery;
var form = layui.form;
+ var layer = layui.layer;
var helper = layui.helper;
var interval = null;
var intervalTime = 15000;
- var position = 0;
var userId = window.koogua.user.id;
+ var requestId = helper.getRequestId();
var chapterId = $('input[name="chapter.id"]').val();
var planId = $('input[name="chapter.plan_id"]').val();
+ var lastPosition = $('input[name="chapter.position"]').val();
var learningUrl = $('input[name="chapter.learning_url"]').val();
+ var danmuListUrl = $('input[name="chapter.danmu_url"]').val();
var playUrls = JSON.parse($('input[name="chapter.play_urls"]').val());
- var requestId = helper.getRequestId();
+ var $danmuText = $('input[name="danmu.text"]');
var options = {
autoplay: false,
@@ -34,17 +37,22 @@ layui.use(['jquery', 'form', 'helper'], function () {
options.listener = function (msg) {
if (msg.type === 'play') {
- start();
+ play();
} else if (msg.type === 'pause') {
- stop();
- } else if (msg.type === 'end') {
- stop();
+ pause();
+ } else if (msg.type === 'ended') {
+ ended();
}
};
var player = new TcPlayer('player', options);
- if (position > 0) {
+ var position = parseInt(lastPosition);
+
+ /**
+ * 过于接近结束位置当作已结束处理
+ */
+ if (position > 0 && player.duration() - position > 10) {
player.currentTime(position);
}
@@ -55,14 +63,9 @@ layui.use(['jquery', 'form', 'helper'], function () {
height: 380
});
- //再添加三个弹幕
- $("#danmu").danmu("addDanmu", [
- {text: "这是滚动弹幕", color: "white", size: 0, position: 0, time: 120}
- , {text: "这是顶部弹幕", color: "yellow", size: 0, position: 1, time: 120}
- , {text: "这是底部弹幕", color: "red", size: 0, position: 2, time: 120}
- ]);
+ initDanmu();
- form.on('checkbox(status)', function (data) {
+ form.on('checkbox(danmu.status)', function (data) {
if (data.elem.checked) {
$('#danmu').danmu('setOpacity', 1);
} else {
@@ -70,35 +73,65 @@ layui.use(['jquery', 'form', 'helper'], function () {
}
});
- form.on('submit(chat)', function (data) {
+ form.on('submit(danmu.send)', function (data) {
$.ajax({
type: 'POST',
url: data.form.action,
data: {
- text: data.field.text,
+ text: $danmuText.val(),
time: player.currentTime(),
- chapter_id: chapterId,
+ chapter_id: chapterId
},
success: function (res) {
- showDanmu(res);
+ $('#danmu').danmu('addDanmu', {
+ text: res.danmu.text,
+ color: res.danmu.color,
+ size: res.danmu.size,
+ time: (res.danmu.time + 1) * 10, //十分之一秒
+ position: res.danmu.position,
+ isnew: 1
+ });
+ $danmuText.val('');
+ },
+ error: function (xhr) {
+ var res = JSON.parse(xhr.responseText);
+ layer.msg(res.msg, {icon: 2});
}
});
return false;
});
- function start() {
+ function clearLearningInterval() {
if (interval != null) {
clearInterval(interval);
interval = null;
}
- interval = setInterval(learning, intervalTime);
- startDanmu();
}
- function stop() {
- clearInterval(interval);
- interval = null;
- pauseDanmu();
+ function setLearningInterval() {
+ interval = setInterval(learning, intervalTime);
+ }
+
+ function play() {
+ startDanmu();
+ clearLearningInterval();
+ setLearningInterval();
+ }
+
+ function pause() {
+ /**
+ * 视频结束也会触发暂停事件,此时弹幕可能尚未结束
+ * 时间差区分暂停是手动还是结束触发
+ */
+ if (player.currentTime() < player.duration() - 5) {
+ pauseDanmu();
+ }
+ clearLearningInterval();
+ }
+
+ function ended() {
+ clearLearningInterval();
+ learning();
}
function learning() {
@@ -107,41 +140,45 @@ layui.use(['jquery', 'form', 'helper'], function () {
type: 'POST',
url: learningUrl,
data: {
- request_id: requestId,
- chapter_id: chapterId,
plan_id: planId,
+ chapter_id: chapterId,
+ request_id: requestId,
interval: intervalTime,
- position: player.currentTime(),
+ position: player.currentTime()
}
});
}
}
function startDanmu() {
- $('#danmu').danmu('danmuStart');
+ $('#danmu').danmu('danmuResume');
}
function pauseDanmu() {
$('#danmu').danmu('danmuPause');
}
- function showDanmu(res) {
- /*
- $('#danmu').danmu('addDanmu', {
- text: res.danmu.text,
- color: res.danmu.color,
- size: res.danmu.size,
- time: res.danmu.time,
- position: res.danmu.position,
- isnew: 1
+ /**
+ * 一次性获取弹幕,待改进为根据时间轴区间获取
+ */
+ function initDanmu() {
+ $.ajax({
+ type: 'GET',
+ url: danmuListUrl,
+ success: function (res) {
+ var items = [];
+ layui.each(res.items, function (index, item) {
+ items.push({
+ text: item.text,
+ color: item.color,
+ size: item.size,
+ time: (item.time + 1) * 10,
+ position: item.position
+ });
+ });
+ $('#danmu').danmu('addDanmu', items);
+ }
});
- */
- $("#danmu").danmu("addDanmu", [
- {text: "这是滚动弹幕", color: "white", size: 0, position: 0, time: 300}
- , {text: "这是顶部弹幕", color: "yellow", size: 0, position: 0, time: 300}
- , {text: "这是底部弹幕", color: "red", size: 0, position: 0, time: 300}
- ]);
- $('input[name=text]').val('');
}
});
\ No newline at end of file