1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-17 15:55:31 +08:00

初步完成直播websocket讨论

This commit is contained in:
xiaochong0302 2020-06-17 20:25:42 +08:00
parent 2dee6efb04
commit b374309c1f
16 changed files with 722 additions and 714 deletions

View File

@ -24,4 +24,12 @@ class IndexController extends Controller
$this->view->setVar('vip_courses', $indexService->getVipCourses());
}
/**
* @Get("/im", name="web.im")
*/
public function imAction()
{
}
}

View File

@ -2,47 +2,81 @@
namespace App\Http\Web\Controllers;
class LiveController extends Controller
use App\Http\Web\Services\Live as LiveService;
use App\Traits\Response as ResponseTrait;
/**
* @RoutePrefix("/live")
*/
class LiveController extends \Phalcon\Mvc\Controller
{
use ResponseTrait;
/**
* @Get("/stats", name="web.live.stats")
* @Get("/{id:[0-9]+}/members", name="web.live.members")
*/
public function statsAction()
public function membersAction($id)
{
$list = [
[
'username' => '直飞机',
'avatar' => 'http://tp1.sinaimg.cn/5619439268/180/40030060651/1',
'status' => 'online',
'sign' => '高舍炮打的准',
'id' => 1,
],
[
'username' => '直飞机2',
'avatar' => 'http://tp1.sinaimg.cn/5619439268/180/40030060651/1',
'status' => 'online',
'sign' => '高舍炮打的准',
'id' => 2,
],
[
'username' => '直飞机3',
'avatar' => 'http://tp1.sinaimg.cn/5619439268/180/40030060651/1',
'status' => 'online',
'sign' => '高舍炮打的准',
'id' => 3,
],
];
$content = ['data' => ['list' => $list]];
return $this->jsonSuccess($content);
}
/**
* @Post("/{id:[0-9]+}/bind", name="web.live.bind")
*/
public function bindAction($id)
{
$service = new LiveService();
$service->bindUser($id);
return $this->jsonSuccess();
}
/**
* @Post("/{id:[0-9]+}/unbind", name="web.live.unbind")
*/
public function unbindAction($id)
{
}
/**
* @Get("/users", name="web.live.users")
* @Post("/{id:[0-9]+}/message", name="web.live.message")
*/
public function usersAction()
public function messageAction($id)
{
$service = new LiveService();
}
/**
* @Post("/login", name="web.live.login")
*/
public function loginAction()
{
}
/**
* @Post("/logout", name="web.live.logout")
*/
public function logoutAction()
{
}
/**
* @Post("/message", name="web.live.message")
*/
public function messageAction()
{
$service->sendMessage($id);
return $this->jsonSuccess();
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Web\Controllers;
use App\Http\Web\Services\Index as IndexService;
class MessengerController extends Controller
{
/**
* @Get("/", name="web.index")
*/
public function indexAction()
{
$this->siteSeo->setKeywords($this->siteSettings['keywords']);
$this->siteSeo->setDescription($this->siteSettings['description']);
$indexService = new IndexService();
$this->view->setVar('slides', $indexService->getSlides());
$this->view->setVar('lives', $indexService->getLives());
$this->view->setVar('new_courses', $indexService->getNewCourses());
$this->view->setVar('free_courses', $indexService->getFreeCourses());
$this->view->setVar('vip_courses', $indexService->getVipCourses());
}
}

View File

@ -53,13 +53,13 @@ class PublicController extends \Phalcon\Mvc\Controller
}
/**
* @Post("/learning", name="web.learning")
* @Post("/{id:[0-9]+}/learning", name="web.learning")
*/
public function learningAction()
public function learningAction($id)
{
$service = new LearningService();
$service->handle();
$service->handle($id);
return $this->jsonSuccess();
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Http\Web\Services;
use App\Services\Frontend\ChapterTrait;
use GatewayClient\Gateway;
class Live extends Service
{
use ChapterTrait;
public function bindUser($id)
{
$chapter = $this->checkChapterCache($id);
$user = $this->getCurrentUser();
$userId = $user->id > 0 ?: $this->session->getId();
$clientId = $this->request->getPost('client_id');
$groupName = $this->getGroupName($chapter->id);
Gateway::$registerAddress = '127.0.0.1:1238';
Gateway::bindUid($clientId, $userId);
Gateway::joinGroup($clientId, $groupName);
}
public function sendMessage($id)
{
$chapter = $this->checkChapterCache($id);
$from = $this->request->getPost('from');
$to = $this->request->getPost('to');
$content = [
'username' => $from['username'],
'avatar' => $from['avatar'],
'content' => $from['content'],
'fromid' => $from['id'],
'id' => $to['id'],
'type' => $to['type'],
'timestamp' => 1000 * time(),
'mine' => false,
];
$message = json_encode([
'type' => 'show_message',
'content' => $content,
]);
$groupName = $this->getGroupName($chapter->id);
Gateway::$registerAddress = '127.0.0.1:1238';
Gateway::sendToGroup($groupName, $message);
}
protected function getGroupName($groupId)
{
return "chapter_{$groupId}";
}
}

View File

@ -2,32 +2,28 @@
{% block content %}
{% set course_url = url({'for':'web.course.show','id':chapter.course.id}) %}
<div class="breadcrumb">
<span class="layui-breadcrumb">
<span><i class="layui-icon layui-icon-return"></i> <a href="{{ course_url }}">返回课程主页</a></span>
</span>
<div class="layui-breadcrumb breadcrumb">
<a href="/">首页</a>
<a href="{{ url({'for':'web.course.list'}) }}">全部课程</a>
<a href="{{ url({'for':'web.course.show','id':chapter.course.id}) }}">{{ chapter.course.title }}</a>
<a><cite>{{ chapter.title }}</cite></a>
</div>
<div class="layout-main clearfix">
<div class="layout-content">
<div id="player" class="container"></div>
</div>
<div class="layout-sidebar">
<div class="sidebar-online container">
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title">
<li class="layui-this">讨论</li>
<li>成员</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show" id="tab-comments" data-url="#"></div>
<div class="layui-tab-item" id="tab-users" data-url="#"></div>
</div>
</div>
</div>
</div>
<div class="live-player container">
<div id="player"></div>
</div>
<div class="layui-hide">
<input type="hidden" name="user.id" value="{{ auth_user.id }}">
<input type="hidden" name="user.name" value="{{ auth_user.name }}">
<input type="hidden" name="user.avatar" value="{{ auth_user.avatar }}">
<input type="hidden" name="chapter.id" value="{{ chapter.id }}">
<input type="hidden" name="chapter.plan_id" value="{{ chapter.me.plan_id }}">
<input type="hidden" name="chapter.play_urls" value='{{ chapter.play_urls|json_encode }}'>
<input type="hidden" name="chapter.learning_url" value="{{ url({'for':'web.learning','id':chapter.id}) }}">
<input type="hidden" name="im.members_url" value="{{ url({'for':'web.live.members','id':chapter.id}) }}">
<input type="hidden" name="im.bind_user_url" value="{{ url({'for':'web.live.bind','id':chapter.id}) }}">
<input type="hidden" name="im.send_msg_url" value="{{ url({'for':'web.live.message','id':chapter.id}) }}">
</div>
{% endblock %}
@ -36,119 +32,12 @@
<script src="//imgcache.qq.com/open/qcloud/video/vcplayer/TcPlayer-2.3.2.js"></script>
{{ js_include('lib/layui/layui.js') }}
{{ js_include('web/js/live.player.js') }}
{{ js_include('web/js/live.im.js') }}
{% endblock %}
{% block inline_js %}
<script>
var interval = null;
var intervalTime = 5000;
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 options = {
live: true,
autoplay: true,
h5_flv: true,
width: 760,
height: 450
};
if (playUrls.rtmp && playUrls.rtmp.od) {
options.rtmp = playUrls.rtmp.od;
}
if (playUrls.rtmp && playUrls.rtmp.hd) {
options.rtmp_hd = playUrls.rtmp.hd;
}
if (playUrls.rtmp && playUrls.rtmp.sd) {
options.rtmp_sd = playUrls.rtmp.sd;
}
if (playUrls.flv && playUrls.flv.od) {
options.flv = playUrls.flv.od;
}
if (playUrls.flv && playUrls.flv.hd) {
options.flv_hd = playUrls.flv.hd;
}
if (playUrls.flv && playUrls.flv.sd) {
options.flv_sd = playUrls.flv.sd;
}
if (playUrls.m3u8 && playUrls.m3u8.od) {
options.m3u8 = playUrls.m3u8.od;
}
if (playUrls.m3u8 && playUrls.m3u8.hd) {
options.m3u8_hd = playUrls.m3u8.hd;
}
if (playUrls.m3u8 && playUrls.m3u8.sd) {
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();
}
}
}
var player = new TcPlayer('player', options);
if (position > 0) {
player.currentTime(position);
}
function start() {
if (interval != null) {
clearInterval(interval);
interval = null;
}
interval = setInterval(learning, intervalTime);
}
function stop() {
clearInterval(interval);
interval = null;
}
function learning() {
$.ajax({
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(),
}
});
}
function getRequestId() {
var id = Date.now().toString(36);
id += Math.random().toString(36).substr(3);
return id;
}
</script>
{% endblock %}

View File

@ -0,0 +1,99 @@
{% extends 'templates/full.volt' %}
{% block content %}
{% endblock %}
{% block inline_js %}
{{ js_include('lib/layui/layui.js') }}
<script>
layui.use(['jquery', 'layim'], function () {
var bindUserUrl = '/live/42425/bind';
var sendMessageUrl = '/live/42425/message';
var socket = new WebSocket('ws://127.0.0.1:8282');
var $ = layui.jquery;
var layim = layui.layim;
layim.config({
brief: true,
init: {
mine: {
'username': '直飞机',
'avatar': 'http://tp1.sinaimg.cn/5619439268/180/40030060651/1',
'status': 'online',
'sign': '高舍炮打的准',
'id': 1,
}
},
members: {
url: '/live/members',
data: {
chapter_id: 123
}
}
}).chat({
name: '直播讨论',
type: 'group',
avatar: 'http://tp1.sinaimg.cn/5619439268/180/40030060651/1',
id: 123,
});
layim.on('sendMessage', function (res) {
sendMessage(res.mine, res.to);
});
socket.onopen = function () {
console.log('socket connect success');
};
socket.onclose = function () {
console.log('socket connect close');
};
socket.onerror = function () {
console.log('socket connect error');
};
socket.onmessage = function (e) {
var data = JSON.parse(e.data);
console.log(data);
if (data.type === 'ping') {
socket.send('pong...');
} else if (data.type === 'bind_user') {
bindUser(data.client_id);
} else if (data.type === 'show_message') {
showMessage(data.content);
}
};
function bindUser(clientId) {
$.ajax({
type: 'POST',
url: bindUserUrl,
data: {client_id: clientId}
});
}
function sendMessage(from, to) {
$.ajax({
type: 'POST',
dataType: 'json',
url: sendMessageUrl,
data: {from: from, to: to}
});
}
function showMessage(message) {
layim.getMessage(message);
}
});
</script>
{% endblock %}

View File

@ -1,338 +0,0 @@
<div id="video" style="width: 600px; height: 400px;">
</div>
{{ js_include('library/chplayer/chplayer.min.js') }}
<script>
var videoObject = {
container: '#video', //容器的ID
variable: 'player',
autoplay: true, //是否自动播放
loaded: 'loadedHandler', //当播放器加载后执行的函数
video: 'http://vod-cdn.koogua.com/20171012_fixed.m3u8'
//video: 'http://www.flashls.org/playlists/test_001/stream_1000k_48k_640x360.m3u8'
};
var player = new chplayer(videoObject);
function loadedHandler() {
changeText('.playerstate', '状态:播放器已加载,建立监听:监听元数据,监听其它状态');
player.addListener('error', errorHandler); //监听视频加载出错
player.addListener('loadedmetadata', loadedMetaDataHandler); //监听元数据
player.addListener('play', playHandler); //监听暂停播放
player.addListener('pause', pauseHandler); //监听暂停播放
player.addListener('timeupdate', timeUpdateHandler); //监听播放时间
player.addListener('seeking', seekingHandler); //监听跳转播放
player.addListener('seeked', seekedHandler); //监听跳转播放完成
player.addListener('volumechange', volumeChangeHandler); //监听音量改变
player.addListener('full', fullHandler); //监听全屏/非全屏切换
player.addListener('ended', endedHandler); //监听全屏/非全屏切换
player.addListener('videochange', videoChangeHandler); //监听视频地址改变
}
function errorHandler() {
changeText('.playerstate', '状态:视频加载错误,停止执行其它动作,等待其它操作');
}
function loadedMetaDataHandler() {
var metaData = player.getMetaDate();
var html = '';
if(parseInt(metaData['videoWidth']) > 0) {
changeText('.playerstate', '状态:获取到元数据信息,如果数据错误,可以使用延迟获取');
html += '总时间:' + metaData['duration'] + '秒,';
html += '音量:' + metaData['volume'] + '范围0-1';
html += '播放器的宽度:' + metaData['width'] + 'px';
html += '播放器的高度:' + metaData['height'] + 'px';
html += '视频的实际宽度:' + metaData['videoWidth'] + 'px';
html += '视频的实际高度:' + metaData['videoHeight'] + 'px';
html += '是否暂停状态:' + metaData['paused'];
} else {
changeText('.playerstate', '状态:未正确获取到元数据信息');
html = '您正在使用移动端或iPad观看本页面该平台限制了视频加载只有在点击了播放器后才能加载视频及获取元数据信息';
}
changeText('.metadata', html);
}
function playHandler() {
//player.animateResume();//继续播放所有弹幕
changeText('.playstate', '播放状态:播放');
window.setTimeout(function() {
loadedMetaDataHandler();
}, 1000);
loadedMetaDataHandler();
}
function pauseHandler() {
//player.animatePause();//暂停所有弹幕
changeText('.playstate', '播放状态:暂停');
loadedMetaDataHandler();
}
function timeUpdateHandler() {
changeText('.currenttimestate', '当前播放时间(秒):' + player.time);
}
function seekingHandler() {
changeText('.seekstate', '跳转动作:正在进行跳转');
}
function seekedHandler() {
changeText('.seekstate', '跳转动作:跳转完成');
}
function volumeChangeHandler() {
changeText('.volumechangestate', '当前音量:' + player.volume);
}
function fullHandler() {
var html = player.getByElement('.fullstate').innerHTML;
if(player.full) {
html += ',全屏';
} else {
html += ',否';
}
changeText('.fullstate', html);
}
function endedHandler() {
changeText('.endedstate', '播放结束');
}
var videoChangeNum = 0;
function videoChangeHandler() {
videoChangeNum++;
changeText('.videochangestate', '视频地址改变了' + videoChangeNum + '次');
}
function seekTime() {
var time = parseInt(player.getByElement('.seektime').value);
var metaData = player.getMetaDate();
var duration = metaData['duration'];
if(time < 0) {
alert('请填写大于0的数字');
return;
}
if(time > duration) {
alert('请填写小于' + duration + '的数字');
return;
}
player.seek(time);
}
function changeVolume() {
var volume = player.getByElement('.changevolume').value;
volume = Math.floor(volume * 100) / 100;
if(volume < 0) {
alert('请填写大于0的数字');
return;
}
if(volume > 1) {
alert('请填写小于1的数字');
return;
}
player.changeVolume(volume);
}
function changeSize() {
player.changeSize(w, h)
}
function frontFun() {
alert('点击了前一集');
}
function nextFun() {
alert('点击了下一集');
}
function newVideo() {
var videoUrl = player.getByElement('.videourl').value;
changeVideo(videoUrl);
}
function newVideo2() {
var videoUrl = player.getByElement('.videourl2').value;
changeVideo(videoUrl);
}
function changeVideo(videoUrl) {
if(player == null) {
return;
}
var newVideoObject = {
container: '#video', //容器的ID
variable: 'player',
autoplay: true, //是否自动播放
loaded: 'loadedHandler', //当播放器加载后执行的函数
video: videoUrl
};
//判断是需要重新加载播放器还是直接换新地址
if(player.playerType == 'html5video') {
if(player.getFileExt(videoUrl) == '.flv' || player.getFileExt(videoUrl) == '.m3u8' || player.getFileExt(videoUrl) == '.f4v' || videoUrl.substr(0, 4) == 'rtmp') {
player.removeChild();
player = null;
player = new chplayer();
player.embed(newVideoObject);
} else {
player.newVideo(newVideoObject);
}
} else {
if(player.getFileExt(videoUrl) == '.mp4' || player.getFileExt(videoUrl) == '.webm' || player.getFileExt(videoUrl) == '.ogg') {
player = null;
player = new chplayer();
player.embed(newVideoObject);
} else {
player.newVideo(newVideoObject);
}
}
}
function newElement() {
var attribute = {
list: [{
type: 'image',
url: 'screenshot/logo.png',
radius: 30, //圆角弧度
width: 30, //定义宽,必需要定义
height: 30, //定义高,必需要定义
alpha: 0.9, //透明度
marginLeft: 10,
marginRight: 10,
marginTop: 10,
marginBottom: 10
},
{
type: 'text', //说明是文本
text: '这里是一个普通的元件不支持HTML', //文本内容
fontColor: '#FFFFFF',
fontSize: 14,
fontFamily: '"Microsoft YaHei", YaHei, "微软雅黑", SimHei,"\5FAE\8F6F\96C5\9ED1", "黑体",Arial',
lineHeight: 30,
alpha: 1, //透明度
//paddingLeft:10,//左边距离
paddingRight: 10, //右边距离
paddingTop: 0,
paddingBottom: 0,
marginLeft: 0,
marginRight: 0,
marginTop: 10,
marginBottom: 0,
//backgroundColor:'#000000',
backAlpha: 0.5,
backRadius: 30 //背景圆角弧度
}
],
x: 10, //x轴坐标
y: 10, //y轴坐标
//position:[1,1],//位置[x轴对齐方式0=左1=中2=右y轴对齐方式0=上1=中2=下x轴偏移量不填写或null则自动判断第一个值为0=紧贴左边1=中间对齐2=贴合右边y轴偏移量不填写或null则自动判断0=紧贴上方1=中间对齐2=紧贴下方)]
alpha: 1,
backgroundColor: '#000000',
backAlpha: 0.5,
backRadius: 60 //背景圆角弧度
};
var el = player.addElement(attribute);
}
function newDanmu() {
//弹幕说明
var danmuObj = {
list: [{
type: 'image',
url: 'screenshot/logo.png',
radius: 30, //圆角弧度
width: 30, //定义宽,必需要定义
height: 30, //定义高,必需要定义
alpha: 0.9, //透明度
marginLeft: 10,
marginRight: 10,
marginTop: 0,
marginBottom: 0
},
{
type: 'text', //说明是文本
text: '演示弹幕内容弹幕只支持普通文本不支持HTML', //文本内容
fontColor: '#FFFFFF',
fontSize: 14,
fontFamily: '"Microsoft YaHei", YaHei, "微软雅黑", SimHei,"\5FAE\8F6F\96C5\9ED1", "黑体",Arial',
lineHeight: 30,
alpha: 1, //透明度
paddingLeft: 10, //左边距离
paddingRight: 10, //右边距离
paddingTop: 0,
paddingBottom: 0,
marginLeft: 0,
marginRight: 0,
marginTop: 0,
marginBottom: 0,
backgroundColor: '#000000',
backAlpha: 0.5,
backRadius: 30 //背景圆角弧度
}
],
x: '100%', //x轴坐标
y: "50%", //y轴坐标
//position:[2,1,0],//位置[x轴对齐方式0=左1=中2=右y轴对齐方式0=上1=中2=下x轴偏移量不填写或null则自动判断第一个值为0=紧贴左边1=中间对齐2=贴合右边y轴偏移量不填写或null则自动判断0=紧贴上方1=中间对齐2=紧贴下方)]
alpha: 1,
//backgroundColor:'#FFFFFF',
backAlpha: 0.8,
backRadius: 30 //背景圆角弧度
};
var danmu = player.addElement(danmuObj);
var danmuS = player.getElement(danmu);
var obj = {
element: danmu,
parameter: 'x',
static: true, //是否禁止其它属性true=是即当x(y)(alpha)变化时y(x)(x,y)在播放器尺寸变化时不允许变化
effect: 'None.easeOut',
start: null,
end: -danmuS['width'],
speed: 10,
overStop: true,
pauseStop: true,
callBack: 'deleteChild'
};
var danmuAnimate = player.animate(obj);
}
function deleteChild(ele) {
if(player) {
player.deleteElement(ele);
}
}
function changeText(div, text) {
player.getByElement(div).innerHTML = text;
}
</script>
<p>
<a href="http://www.chplayer.com/" target="_blank">官网chplayer.com</a>,版本号1.0</p>
<p>以下仅列出部分功能,全部功能请至官网<a href="http://www.chplayer.com/manual/" target="_blank">《手册》</a>查看</p>
<p>
<button type="button" onclick="player.play()">播放</button>
<button type="button" onclick="player.pause()">暂停</button>
<button type="button" onclick="player.playOrPause()">播放/暂停</button>
<button type="button" onclick="loadedMetaDataHandler()">获取元数据</button>
<button type="button" onclick="newElement()">添加元件</button>
<button type="button" onclick="newDanmu()">添加弹幕</button>
<a href="http://www.chplayer.com/manual/animate.html" target="_blank">更多弹幕动作</a>
</p>
<p class="metadata"></p>
<p>单独监听功能:</p>
<p class="handler">
<span class="playstate">播放状态:暂停</span><br />
<span class="seekstate">无跳转时间</span><br />
<span class="volumechangestate">当前音量0.8</span><br />
<span class="fullstate">是否全屏:否</span><br />
<span class="endedstate">还未结束</span><br />
<span class="videochangestate">视频地址正常</span><br />
<span class="currenttimestate">当前播放时间0</span>
</p>

View File

@ -14,11 +14,11 @@ class Learning extends FrontendService
use ChapterTrait;
public function handle()
public function handle($id)
{
$post = $this->request->getPost();
$chapter = $this->checkChapterCache($post['chapter_id']);
$chapter = $this->checkChapterCache($id);
$user = $this->getLoginUser();

View File

@ -52,6 +52,7 @@ trait Auth
$user->id = 0;
$user->name = 'guest';
$user->avatar = kg_ci_avatar_img_url(null);
return $user;
}

View File

@ -14,6 +14,7 @@
"qcloudsms/qcloudsms_php": "0.1.*",
"qcloud/cos-sdk-v5": "2.*",
"workerman/gateway-worker": "^3.0.12",
"workerman/gatewayclient": "^3.0.12",
"whichbrowser/parser": "^2.0",
"hightman/xunsearch": "^1.4.14",
"aferrandini/phpqrcode": "1.0.1",

409
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1de77fca92539b23fbdbad81127bafd1",
"content-hash": "d847018715ab8103c3b5e04db1807223",
"packages": [
{
"name": "aferrandini/phpqrcode",
@ -58,25 +58,25 @@
"time": "2013-07-08T09:39:08+00:00"
},
{
"name": "doctrine/lexer",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/lexer.git",
"reference": "e17f069ede36f7534b95adec71910ed1b49c74ea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/lexer/zipball/e17f069ede36f7534b95adec71910ed1b49c74ea",
"reference": "e17f069ede36f7534b95adec71910ed1b49c74ea",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"name": "doctrine/lexer",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/lexer.git",
"reference": "e17f069ede36f7534b95adec71910ed1b49c74ea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/lexer/zipball/e17f069ede36f7534b95adec71910ed1b49c74ea",
"reference": "e17f069ede36f7534b95adec71910ed1b49c74ea",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^7.2"
},
@ -703,16 +703,16 @@
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
"keywords": [
"cron",
@ -1898,35 +1898,68 @@
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
"MIT"
],
"homepage": "http://www.workerman.net",
"keywords": [
"communication",
"distributed"
],
"time": "2019-07-02T11:55:24+00:00"
"homepage": "http://www.workerman.net",
"keywords": [
"communication",
"distributed"
],
"time": "2019-07-02T11:55:24+00:00"
},
{
"name": "workerman/workerman",
"version": "v3.5.22",
"source": {
"type": "git",
"url": "https://github.com/walkor/Workerman.git",
"reference": "488f108f9e446f31bac4d689bb9f9fe3705862cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/Workerman/zipball/488f108f9e446f31bac4d689bb9f9fe3705862cf",
"reference": "488f108f9e446f31bac4d689bb9f9fe3705862cf",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
{
"name": "workerman/gatewayclient",
"version": "v3.0.13",
"source": {
"type": "git",
"url": "https://github.com/walkor/GatewayClient.git",
"reference": "6f4e76f38947be5cabca2c6fee367151f248d949"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/GatewayClient/zipball/6f4e76f38947be5cabca2c6fee367151f248d949",
"reference": "6f4e76f38947be5cabca2c6fee367151f248d949",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"type": "library",
"autoload": {
"psr-4": {
"GatewayClient\\": "./"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"homepage": "http://www.workerman.net",
"time": "2018-09-15T03:03:50+00:00"
},
{
"name": "workerman/workerman",
"version": "v3.5.22",
"source": {
"type": "git",
"url": "https://github.com/walkor/Workerman.git",
"reference": "488f108f9e446f31bac4d689bb9f9fe3705862cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/Workerman/zipball/488f108f9e446f31bac4d689bb9f9fe3705862cf",
"reference": "488f108f9e446f31bac4d689bb9f9fe3705862cf",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.3"
},
@ -1951,14 +1984,14 @@
"role": "Developer"
}
],
"description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
"homepage": "http://www.workerman.net",
"keywords": [
"asynchronous",
"event-loop"
],
"time": "2019-09-06T03:42:47+00:00"
},
"description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
"homepage": "http://www.workerman.net",
"keywords": [
"asynchronous",
"event-loop"
],
"time": "2019-09-06T03:42:47+00:00"
},
{
"name": "xiaochong0302/ip2region",
"version": "v1.0.0",
@ -1979,16 +2012,16 @@
}
]
},
"require": {
"php": ">=7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Koogua\\Ip2Region\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"require": {
"php": ">=7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Koogua\\Ip2Region\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
@ -2054,13 +2087,13 @@
"email": "me@yansongda.cn"
}
],
"description": "专注 Alipay 和 WeChat 的支付扩展包",
"keywords": [
"alipay",
"pay",
"wechat"
],
"time": "2019-09-21T15:05:57+00:00"
"description": "专注 Alipay 和 WeChat 的支付扩展包",
"keywords": [
"alipay",
"pay",
"wechat"
],
"time": "2019-09-21T15:05:57+00:00"
},
{
"name": "yansongda/supports",
@ -2082,16 +2115,16 @@
}
]
},
"require": {
"guzzlehttp/guzzle": "^6.2",
"monolog/monolog": "^1.23 || ^2.0",
"php": ">=7.1.3"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.15",
"phpunit/phpunit": "^7.5",
"predis/predis": "^1.1"
},
"require": {
"guzzlehttp/guzzle": "^6.2",
"monolog/monolog": "^1.23 || ^2.0",
"php": ">=7.1.3"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.15",
"phpunit/phpunit": "^7.5",
"predis/predis": "^1.1"
},
"suggest": {
"predis/predis": "Allows to use throttle feature"
},
@ -2347,16 +2380,16 @@
}
]
},
"require": {
"cakephp/core": "^3.6.0",
"php": ">=5.6.0",
"psr/simple-cache": "^1.0.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Cake\\Cache\\": "."
}
"require": {
"cakephp/core": "^3.6.0",
"php": ">=5.6.0",
"psr/simple-cache": "^1.0.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Cake\\Cache\\": "."
}
},
"notification-url": "https://packagist.jp/downloads/",
"license": [
@ -2695,15 +2728,15 @@
],
"description": "CakePHP Utility classes such as Inflector, String, Hash, and Security",
"homepage": "https://cakephp.org",
"keywords": [
"cakephp",
"hash",
"inflector",
"security",
"string",
"utility"
],
"time": "2019-11-21T14:18:54+00:00"
"keywords": [
"cakephp",
"hash",
"inflector",
"security",
"string",
"utility"
],
"time": "2019-11-21T14:18:54+00:00"
},
{
"name": "jaeger/g-http",
@ -2962,16 +2995,16 @@
}
]
},
"require": {
"php": "^7.1",
"riimu/kit-phpencoder": "^2.4",
"robmorgan/phinx": "^0.11",
"symfony/console": "^2.8|^3.0|^4.0|^5.0"
},
"require-dev": {
"overtrue/phplint": "^1.1",
"phpstan/phpstan-shim": "^0.11",
"phpunit/phpunit": "^7.0",
"require": {
"php": "^7.1",
"riimu/kit-phpencoder": "^2.4",
"robmorgan/phinx": "^0.11",
"symfony/console": "^2.8|^3.0|^4.0|^5.0"
},
"require-dev": {
"overtrue/phplint": "^1.1",
"phpstan/phpstan-shim": "^0.11",
"phpunit/phpunit": "^7.0",
"squizlabs/php_codesniffer": "^3.4"
},
"bin": [
@ -2989,16 +3022,16 @@
],
"description": "Migration generator for Phinx",
"homepage": "https://github.com/odan/phinx-migrations-generator",
"keywords": [
"database",
"generator",
"migration",
"migrations",
"mysql",
"phinx"
],
"time": "2019-11-21T15:15:19+00:00"
},
"keywords": [
"database",
"generator",
"migration",
"migrations",
"mysql",
"phinx"
],
"time": "2019-11-21T15:15:19+00:00"
},
{
"name": "phalcon/ide-stubs",
"version": "v3.4.3",
@ -3054,25 +3087,25 @@
"time": "2018-12-09T14:11:06+00:00"
},
{
"name": "psr/container",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"name": "psr/container",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.3.0"
},
@ -3692,15 +3725,15 @@
],
"description": "Generic abstractions related to writing services",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"time": "2019-11-18T17:27:11+00:00"
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"time": "2019-11-18T17:27:11+00:00"
},
{
"name": "symfony/var-dumper",
@ -3785,25 +3818,25 @@
"time": "2019-12-18T13:41:29+00:00"
},
{
"name": "symfony/yaml",
"version": "v5.0.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "51b684480184fa767b97e28eaca67664e48dd3e9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/51b684480184fa767b97e28eaca67664e48dd3e9",
"reference": "51b684480184fa767b97e28eaca67664e48dd3e9",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"name": "symfony/yaml",
"version": "v5.0.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "51b684480184fa767b97e28eaca67664e48dd3e9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/51b684480184fa767b97e28eaca67664e48dd3e9",
"reference": "51b684480184fa767b97e28eaca67664e48dd3e9",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^7.2.5",
"symfony/polyfill-ctype": "~1.8"
@ -3835,16 +3868,16 @@
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2019-11-18T17:27:11+00:00"

View File

@ -727,6 +727,11 @@
color: gray;
}
.live-player {
width: 800px;
height: 450px;
}
.chapter-bg {
background-color: #f2f2f2;
}

View File

@ -0,0 +1,96 @@
layui.use(['jquery', 'layim'], function () {
var $ = layui.jquery;
var layim = layui.layim;
var socket = new WebSocket('ws://127.0.0.1:8282');
var membersUrl = $('input[name="im.members_url"]').val();
var bindUserUrl = $('input[name="im.bind_user_url"]').val();
var sendMsgUrl = $('input[name="im.send_msg_url"]').val();
var group = {
id: $('input[name="chapter.id"]').val(),
avatar: 'http://tp1.sinaimg.cn/5619439268/180/40030060651/1',
name: '直播讨论'
};
var user = {
id: $('input[name="user.id"]').val(),
name: $('input[name="user.name"]').val(),
avatar: $('input[name="user.avatar"]').val(),
status: 'online',
sign: ''
};
layim.config({
brief: true,
init: {
mine: {
'username': user.name,
'avatar': user.avatar,
'id': user.id,
'status': user.status,
'sign': user.sign
}
},
members: {
url: membersUrl
}
}).chat({
type: 'group',
name: group.name,
avatar: group.avatar,
id: group.id
});
layim.on('sendMessage', function (res) {
sendMessage(res.mine, res.to);
});
socket.onopen = function () {
console.log('socket connect success');
};
socket.onclose = function () {
console.log('socket connect close');
};
socket.onerror = function () {
console.log('socket connect error');
};
socket.onmessage = function (e) {
var data = JSON.parse(e.data);
console.log(data);
if (data.type === 'ping') {
socket.send('pong...');
} else if (data.type === 'bind_user') {
bindUser(data.client_id);
} else if (data.type === 'show_message') {
showMessage(data.content);
}
};
function bindUser(clientId) {
$.ajax({
type: 'POST',
url: bindUserUrl,
data: {client_id: clientId}
});
}
function sendMessage(from, to) {
$.ajax({
type: 'POST',
dataType: 'json',
url: sendMsgUrl,
data: {from: from, to: to}
});
}
function showMessage(message) {
layim.getMessage(message);
}
});

View File

@ -0,0 +1,104 @@
var interval = null;
var intervalTime = 5000;
var position = 0;
var chapterId = $('input[name="chapter.id"]').val();
var planId = $('input[name="chapter.plan_id"]').val();
var userId = $('input[name="user.id"]').val();
var learningUrl = $('input[name="chapter.learning_url"]').val();
var playUrls = JSON.parse($('input[name="chapter.play_urls"]').val());
var requestId = getRequestId();
var options = {
live: true,
autoplay: true,
h5_flv: true,
width: 800,
height: 450
};
if (playUrls.rtmp && playUrls.rtmp.od) {
options.rtmp = playUrls.rtmp.od;
}
if (playUrls.rtmp && playUrls.rtmp.hd) {
options.rtmp_hd = playUrls.rtmp.hd;
}
if (playUrls.rtmp && playUrls.rtmp.sd) {
options.rtmp_sd = playUrls.rtmp.sd;
}
if (playUrls.flv && playUrls.flv.od) {
options.flv = playUrls.flv.od;
}
if (playUrls.flv && playUrls.flv.hd) {
options.flv_hd = playUrls.flv.hd;
}
if (playUrls.flv && playUrls.flv.sd) {
options.flv_sd = playUrls.flv.sd;
}
if (playUrls.m3u8 && playUrls.m3u8.od) {
options.m3u8 = playUrls.m3u8.od;
}
if (playUrls.m3u8 && playUrls.m3u8.hd) {
options.m3u8_hd = playUrls.m3u8.hd;
}
if (playUrls.m3u8 && playUrls.m3u8.sd) {
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();
}
}
}
var player = new TcPlayer('player', options);
if (position > 0) {
player.currentTime(position);
}
function start() {
if (interval != null) {
clearInterval(interval);
interval = null;
}
interval = setInterval(learning, intervalTime);
}
function stop() {
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(),
}
});
}
function getRequestId() {
var id = Date.now().toString(36);
id += Math.random().toString(36).substr(3);
return id;
}

View File

@ -38,8 +38,8 @@ class Events
public static function onConnect($clientId)
{
$message = json_encode([
'type' => 'welcome',
'message' => 'just enjoy it',
'type' => 'bind_user',
'client_id' => $clientId,
]);
Gateway::sendToClient($clientId, $message);
@ -53,25 +53,7 @@ class Events
*/
public static function onMessage($clientId, $message)
{
$content = json_decode($message, true);
if (!isset($content['type'])) return;
if ($content['type'] == 'join_group') {
$_SESSION['group'] = $content['group'];
Gateway::joinGroup($clientId, $content['group']);
} elseif ($content['type'] == 'send_danmaku') {
$message = [
'type' => 'show_danmaku',
'danmaku' => $content['danmaku'],
];
Gateway::sendToGroup($_SESSION['group'], json_encode($message), [$clientId]);
}
}
/**