mirror of
https://github.com/kuaifan/dootask.git
synced 2026-01-12 08:58:11 +00:00
feat: 添加会议功能
This commit is contained in:
parent
24ed87cc82
commit
0f8cfa72b6
@ -8,6 +8,7 @@ use App\Models\User;
|
||||
use App\Models\UserEmailVerification;
|
||||
use App\Models\UserTransfer;
|
||||
use App\Models\WebSocket;
|
||||
use App\Module\AgoraIO\AgoraTokenGenerator;
|
||||
use App\Module\Base;
|
||||
use Arr;
|
||||
use Cache;
|
||||
@ -760,4 +761,43 @@ class UsersController extends AbstractController
|
||||
return Base::retError('not exist');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/users/agoraio/token 16. 【agoraio】获取 token
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName agoraio__token
|
||||
*
|
||||
* @apiParam {Number} dialog_id 会话ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function agoraio__token()
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$appid = '342c604542484b0d9659527f79aefcdb';
|
||||
$app_certificate = '920eb911c1f549948366e44d6dcabcbe';
|
||||
$channel = "DooTask:" . md5(env("APP_KEY"));
|
||||
$uid = $user->userid;
|
||||
try {
|
||||
$service = new AgoraTokenGenerator($appid, $app_certificate, $channel, $uid);
|
||||
} catch (\Exception $e) {
|
||||
return Base::retError($e->getMessage());
|
||||
}
|
||||
$token = $service->buildToken();
|
||||
if (empty($token)) {
|
||||
return Base::retError('Generated token failed');
|
||||
}
|
||||
return Base::retSuccess('success', [
|
||||
'appid' => $appid,
|
||||
'channel' => $channel,
|
||||
'uid' => $uid,
|
||||
'token' => $token
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
193
app/Module/AgoraIO/AccessToken.php
Normal file
193
app/Module/AgoraIO/AccessToken.php
Normal file
@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/**
|
||||
* @Description :
|
||||
*
|
||||
* @Date : 2019-03-14 13:22
|
||||
* @Author : hmy940118@gmail.com
|
||||
*/
|
||||
|
||||
namespace App\Module\AgoraIO;
|
||||
|
||||
class AccessToken
|
||||
{
|
||||
|
||||
const Privileges = array(
|
||||
"kJoinChannel" => 1,
|
||||
"kPublishAudioStream" => 2,
|
||||
"kPublishVideoStream" => 3,
|
||||
"kPublishDataStream" => 4,
|
||||
"kPublishAudioCdn" => 5,
|
||||
"kPublishVideoCdn" => 6,
|
||||
"kRequestPublishAudioStream" => 7,
|
||||
"kRequestPublishVideoStream" => 8,
|
||||
"kRequestPublishDataStream" => 9,
|
||||
"kInvitePublishAudioStream" => 10,
|
||||
"kInvitePublishVideoStream" => 11,
|
||||
"kInvitePublishDataStream" => 12,
|
||||
"kAdministrateChannel" => 101
|
||||
);
|
||||
|
||||
public $appID, $appCertificate, $channelName, $uid;
|
||||
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* AccessToken constructor.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = new Message();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $uid
|
||||
*/
|
||||
public function setUid($uid)
|
||||
{
|
||||
if ($uid === 0) {
|
||||
$this->uid = "";
|
||||
} else {
|
||||
$this->uid = $uid . '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $str
|
||||
* @return bool
|
||||
*/
|
||||
public function is_nonempty_string($name, $str)
|
||||
{
|
||||
if (is_string($str) && $str !== "") {
|
||||
return true;
|
||||
}
|
||||
echo $name . " check failed, should be a non-empty string";
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $appID
|
||||
* @param $appCertificate
|
||||
* @param $channelName
|
||||
* @param $uid
|
||||
* @return AccessToken|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function init($appID, $appCertificate, $channelName, $uid)
|
||||
{
|
||||
$accessToken = new AccessToken();
|
||||
if (!$accessToken->is_nonempty_string("appID", $appID) ||
|
||||
!$accessToken->is_nonempty_string("appCertificate", $appCertificate) ||
|
||||
!$accessToken->is_nonempty_string("channelName", $channelName)) {
|
||||
return null;
|
||||
}
|
||||
$accessToken->appID = $appID;
|
||||
$accessToken->appCertificate = $appCertificate;
|
||||
$accessToken->channelName = $channelName;
|
||||
$accessToken->setUid($uid);
|
||||
$accessToken->message = new Message();
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $token
|
||||
* @param $appCertificate
|
||||
* @param $channel
|
||||
* @param $uid
|
||||
* @return AccessToken|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function initWithToken($token, $appCertificate, $channel, $uid)
|
||||
{
|
||||
$accessToken = new AccessToken();
|
||||
if (!$accessToken->extract($token, $appCertificate, $channel, $uid)) {
|
||||
return null;
|
||||
}
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @param $expireTimestamp
|
||||
* @return $this
|
||||
*/
|
||||
public function addPrivilege($key, $expireTimestamp)
|
||||
{
|
||||
$this->message->privileges[$key] = $expireTimestamp;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $token
|
||||
* @param $appCertificate
|
||||
* @param $channelName
|
||||
* @param $uid
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function extract($token, $appCertificate, $channelName, $uid)
|
||||
{
|
||||
$ver_len = 3;
|
||||
$appid_len = 32;
|
||||
$version = substr($token, 0, $ver_len);
|
||||
if ($version !== "006") {
|
||||
echo 'invalid version ' . $version;
|
||||
return false;
|
||||
}
|
||||
if (!$this->is_nonempty_string("token", $token) ||
|
||||
!$this->is_nonempty_string("appCertificate", $appCertificate) ||
|
||||
!$this->is_nonempty_string("channelName", $channelName)) {
|
||||
return false;
|
||||
}
|
||||
$appid = substr($token, $ver_len, $appid_len);
|
||||
$content = (base64_decode(substr($token, $ver_len + $appid_len, strlen($token) - ($ver_len + $appid_len))));
|
||||
$pos = 0;
|
||||
$len = unpack("v", $content . substr($pos, 2))[1];
|
||||
$pos += 2;
|
||||
$sig = substr($content, $pos, $len);
|
||||
$pos += $len;
|
||||
$crc_channel = unpack("V", substr($content, $pos, 4))[1];
|
||||
$pos += 4;
|
||||
$crc_uid = unpack("V", substr($content, $pos, 4))[1];
|
||||
$pos += 4;
|
||||
$msgLen = unpack("v", substr($content, $pos, 2))[1];
|
||||
$pos += 2;
|
||||
$msg = substr($content, $pos, $msgLen);
|
||||
$this->appID = $appid;
|
||||
$message = new Message();
|
||||
$message->unpackContent($msg);
|
||||
$this->message = $message;
|
||||
//non reversable values
|
||||
$this->appCertificate = $appCertificate;
|
||||
$this->channelName = $channelName;
|
||||
$this->setUid($uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$msg = $this->message->packContent();
|
||||
$val = array_merge(unpack("C*", $this->appID), unpack("C*", $this->channelName), unpack("C*", $this->uid), $msg);
|
||||
|
||||
$sig = hash_hmac('sha256', implode(array_map("chr", $val)), $this->appCertificate, true);
|
||||
$crc_channel_name = crc32($this->channelName) & 0xffffffff;
|
||||
$crc_uid = crc32($this->uid) & 0xffffffff;
|
||||
$content = array_merge(unpack("C*", $this->packString($sig)), unpack("C*", pack("V", $crc_channel_name)), unpack("C*", pack("V", $crc_uid)), unpack("C*", pack("v", count($msg))), $msg);
|
||||
$version = "006";
|
||||
$ret = $version . $this->appID . base64_encode(implode(array_map("chr", $content)));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return string
|
||||
*/
|
||||
public function packString($value)
|
||||
{
|
||||
return pack("v", strlen($value)) . $value;
|
||||
}
|
||||
}
|
||||
122
app/Module/AgoraIO/AgoraTokenGenerator.php
Normal file
122
app/Module/AgoraIO/AgoraTokenGenerator.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* @Description :
|
||||
*
|
||||
* @Date : 2019-03-14 13:20
|
||||
* @Author : hmy940118@gmail.com
|
||||
*/
|
||||
|
||||
namespace App\Module\AgoraIO;
|
||||
|
||||
|
||||
class AgoraTokenGenerator
|
||||
{
|
||||
const AttendeePrivileges = array(
|
||||
AccessToken::Privileges["kJoinChannel"] => 0,
|
||||
AccessToken::Privileges["kPublishAudioStream"] => 0,
|
||||
AccessToken::Privileges["kPublishVideoStream"] => 0,
|
||||
AccessToken::Privileges["kPublishDataStream"] => 0
|
||||
);
|
||||
|
||||
|
||||
const PublisherPrivileges = array(
|
||||
AccessToken::Privileges["kJoinChannel"] => 0,
|
||||
AccessToken::Privileges["kPublishAudioStream"] => 0,
|
||||
AccessToken::Privileges["kPublishVideoStream"] => 0,
|
||||
AccessToken::Privileges["kPublishDataStream"] => 0,
|
||||
AccessToken::Privileges["kPublishAudioCdn"] => 0,
|
||||
AccessToken::Privileges["kPublishVideoCdn"] => 0,
|
||||
AccessToken::Privileges["kInvitePublishAudioStream"] => 0,
|
||||
AccessToken::Privileges["kInvitePublishVideoStream"] => 0,
|
||||
AccessToken::Privileges["kInvitePublishDataStream"] => 0
|
||||
);
|
||||
|
||||
const SubscriberPrivileges = array(
|
||||
AccessToken::Privileges["kJoinChannel"] => 0,
|
||||
AccessToken::Privileges["kRequestPublishAudioStream"] => 0,
|
||||
AccessToken::Privileges["kRequestPublishVideoStream"] => 0,
|
||||
AccessToken::Privileges["kRequestPublishDataStream"] => 0
|
||||
);
|
||||
|
||||
const AdminPrivileges = array(
|
||||
AccessToken::Privileges["kJoinChannel"] => 0,
|
||||
AccessToken::Privileges["kPublishAudioStream"] => 0,
|
||||
AccessToken::Privileges["kPublishVideoStream"] => 0,
|
||||
AccessToken::Privileges["kPublishDataStream"] => 0,
|
||||
AccessToken::Privileges["kAdministrateChannel"] => 0
|
||||
);
|
||||
const Role = array(
|
||||
"kRoleAttendee" => 0, // for communication
|
||||
"kRolePublisher" => 1, // for live broadcast
|
||||
"kRoleSubscriber" => 2, // for live broadcast
|
||||
"kRoleAdmin" => 101
|
||||
);
|
||||
|
||||
const RolePrivileges = array(
|
||||
self::Role["kRoleAttendee"] => self::AttendeePrivileges,
|
||||
self::Role["kRolePublisher"] => self::PublisherPrivileges,
|
||||
self::Role["kRoleSubscriber"] => self::SubscriberPrivileges,
|
||||
self::Role["kRoleAdmin"] => self::AdminPrivileges
|
||||
);
|
||||
|
||||
public $token;
|
||||
|
||||
/**
|
||||
* AgoraTokenGenerator constructor.
|
||||
* @param $appID
|
||||
* @param $appCertificate
|
||||
* @param $channelName
|
||||
* @param $uid
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($appID, $appCertificate, $channelName, $uid){
|
||||
$this->token = new AccessToken();
|
||||
$this->token->appID = $appID;
|
||||
$this->token->appCertificate = $appCertificate;
|
||||
$this->token->channelName = $channelName;
|
||||
$this->token->setUid($uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $token
|
||||
* @param $appCertificate
|
||||
* @param $channel
|
||||
* @param $uid
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function initWithToken($token, $appCertificate, $channel, $uid){
|
||||
$this->token = AccessToken::initWithToken($token, $appCertificate, $channel, $uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $role
|
||||
*/
|
||||
public function initPrivilege($role){
|
||||
$p = self::RolePrivileges[$role];
|
||||
foreach($p as $key => $value){
|
||||
$this->setPrivilege($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $privilege
|
||||
* @param $expireTimestamp
|
||||
*/
|
||||
public function setPrivilege($privilege, $expireTimestamp){
|
||||
$this->token->addPrivilege($privilege, $expireTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $privilege
|
||||
*/
|
||||
public function removePrivilege($privilege){
|
||||
unset($this->token->message->privileges[$privilege]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function buildToken(){
|
||||
return $this->token->build();
|
||||
}
|
||||
}
|
||||
70
app/Module/AgoraIO/Message.php
Normal file
70
app/Module/AgoraIO/Message.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* @Description :
|
||||
*
|
||||
* @Date : 2019-03-14 13:27
|
||||
* @Author : hmy940118@gmail.com
|
||||
*/
|
||||
|
||||
namespace App\Module\AgoraIO;
|
||||
|
||||
class Message
|
||||
{
|
||||
public $salt;
|
||||
|
||||
public $ts;
|
||||
|
||||
public $privileges;
|
||||
|
||||
/**
|
||||
* Message constructor.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->salt = rand(0, 100000);
|
||||
$date = new \DateTime("now", new \DateTimeZone('UTC'));
|
||||
$this->ts = $date->getTimestamp() + 168 * 3600; // 7天时间
|
||||
$this->privileges = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function packContent()
|
||||
{
|
||||
$buffer = unpack("C*", pack("V", $this->salt));
|
||||
$buffer = array_merge($buffer, unpack("C*", pack("V", $this->ts)));
|
||||
$buffer = array_merge($buffer, unpack("C*", pack("v", sizeof($this->privileges))));
|
||||
foreach ($this->privileges as $key => $value) {
|
||||
$buffer = array_merge($buffer, unpack("C*", pack("v", $key)));
|
||||
$buffer = array_merge($buffer, unpack("C*", pack("V", $value)));
|
||||
}
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $msg
|
||||
*/
|
||||
public function unpackContent($msg)
|
||||
{
|
||||
$pos = 0;
|
||||
$salt = unpack("V", substr($msg, $pos, 4))[1];
|
||||
$pos += 4;
|
||||
$ts = unpack("V", substr($msg, $pos, 4))[1];
|
||||
$pos += 4;
|
||||
$size = unpack("v", substr($msg, $pos, 2))[1];
|
||||
$pos += 2;
|
||||
$privileges = array();
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
$key = unpack("v", substr($msg, $pos, 2));
|
||||
$pos += 2;
|
||||
$value = unpack("V", substr($msg, $pos, 4));
|
||||
$pos += 4;
|
||||
$privileges[$key[1]] = $value[1];
|
||||
}
|
||||
$this->salt = $salt;
|
||||
$this->ts = $ts;
|
||||
$this->privileges = $privileges;
|
||||
}
|
||||
}
|
||||
@ -350,6 +350,9 @@
|
||||
<ProjectArchived v-if="archivedProjectShow"/>
|
||||
</DrawerOverlay>
|
||||
|
||||
<!--会议管理-->
|
||||
<MeetingManager/>
|
||||
|
||||
<!--移动端选项卡-->
|
||||
<transition name="mobile-slide">
|
||||
<MobileTabbar v-if="showMobileTabbar" @on-click="onTabbarClick"/>
|
||||
@ -376,9 +379,11 @@ import {Store} from "le5le-store";
|
||||
import MobileBack from "../components/Mobile/Back";
|
||||
import TaskMenu from "./manage/components/TaskMenu";
|
||||
import MobileNotification from "../components/Mobile/Notification";
|
||||
import MeetingManager from "./manage/components/MeetingManager";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MeetingManager,
|
||||
MobileNotification,
|
||||
TaskMenu,
|
||||
MobileBack,
|
||||
|
||||
@ -51,13 +51,17 @@
|
||||
<ETooltip slot="reference" ref="moreTip" :disabled="!$isDesktop || showMore" placement="top" :content="$L('展开')">
|
||||
<i class="taskfont"></i>
|
||||
</ETooltip>
|
||||
<div v-if="recordReady" class="chat-input-popover-item" @click="onToolbar('meeting')">
|
||||
<i class="taskfont"></i>
|
||||
{{$L('新会议')}}
|
||||
</div>
|
||||
<div class="chat-input-popover-item" @click="onToolbar('image')">
|
||||
<i class="taskfont"></i>
|
||||
{{$L('图片')}}
|
||||
<i class="taskfont"></i>
|
||||
{{$L('发送图片')}}
|
||||
</div>
|
||||
<div class="chat-input-popover-item" @click="onToolbar('file')">
|
||||
<i class="taskfont"></i>
|
||||
{{$L('文件')}}
|
||||
<i class="taskfont"></i>
|
||||
{{$L('上传文件')}}
|
||||
</div>
|
||||
</EPopover>
|
||||
</li>
|
||||
@ -112,6 +116,7 @@ import ChatEmoji from "./emoji";
|
||||
import touchmouse from "../../../../directives/touchmouse";
|
||||
import TransferDom from "../../../../directives/transfer-dom";
|
||||
import clickoutside from "../../../../directives/clickoutside";
|
||||
import {Store} from "le5le-store";
|
||||
|
||||
export default {
|
||||
name: 'ChatInput',
|
||||
@ -781,6 +786,12 @@ export default {
|
||||
this.openMenu("#");
|
||||
break;
|
||||
|
||||
case 'meeting':
|
||||
Store.set('addMeeting', {
|
||||
userids: [this.userId]
|
||||
}); // todo 加入当前会话人员
|
||||
break;
|
||||
|
||||
case 'image':
|
||||
case 'file':
|
||||
this.$emit('on-more', action)
|
||||
|
||||
238
resources/assets/js/pages/manage/components/MeetingManager.vue
Normal file
238
resources/assets/js/pages/manage/components/MeetingManager.vue
Normal file
@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div v-show="false">
|
||||
<Modal
|
||||
v-model="addShow"
|
||||
:title="$L('新会议')"
|
||||
:mask-closable="false">
|
||||
<Form ref="addForm" :model="addData" :rules="addRule" label-width="auto" @submit.native.prevent>
|
||||
<FormItem prop="userids" :label="$L('会议成员')">
|
||||
<UserInput v-model="addData.userids" :multiple-max="10" :placeholder="$L('选择会议成员')"/>
|
||||
</FormItem>
|
||||
<FormItem prop="video" :label="$L('开启视频')">
|
||||
<RadioGroup v-model="addData.video">
|
||||
<Radio label="open">{{$L('开启')}}</Radio>
|
||||
<Radio label="close">{{$L('关闭')}}</Radio>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div slot="footer" class="adaption">
|
||||
<Button type="default" @click="addShow=false">{{$L('取消')}}</Button>
|
||||
<Button type="primary" :loading="loadIng > 0" @click="onSubmit">{{$L('开始会议')}}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal
|
||||
v-model="meetingShow"
|
||||
:title="$L('会议中')"
|
||||
:mask="false"
|
||||
:mask-closable="false"
|
||||
:closable="false"
|
||||
:transition-names="['', '']"
|
||||
class-name="meeting-manager"
|
||||
fullscreen>
|
||||
<ul>
|
||||
<li v-if="localTracks.uid">
|
||||
<MeetingPlayer :player="localTracks"/>
|
||||
</li>
|
||||
<li v-for="item in remoteUsers">
|
||||
<MeetingPlayer :player="item"/>
|
||||
</li>
|
||||
</ul>
|
||||
<div slot="footer" class="adaption">
|
||||
<Button type="warning" :loading="loadIng > 0" @click="onClose">{{$L('退出会议')}}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UserInput from "../../../components/UserInput";
|
||||
import {Store} from "le5le-store";
|
||||
import {mapState} from "vuex";
|
||||
import MeetingPlayer from "./MeetingPlayer";
|
||||
|
||||
export default {
|
||||
name: "MeetingManager",
|
||||
components: {MeetingPlayer, UserInput},
|
||||
data() {
|
||||
return {
|
||||
loadIng: 0,
|
||||
subscribe: null,
|
||||
|
||||
addShow: false,
|
||||
addData: {
|
||||
userids: [],
|
||||
video: 'close'
|
||||
},
|
||||
addRule: {},
|
||||
|
||||
meetingShow: false,
|
||||
|
||||
agoraClient: null,
|
||||
remoteUsers: [],
|
||||
localTracks: {
|
||||
uid: null,
|
||||
mediaType: null,
|
||||
audioTrack: null,
|
||||
videoTrack: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.subscribe = Store.subscribe('addMeeting', this.onAdd);
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.subscribe) {
|
||||
this.subscribe.unsubscribe();
|
||||
this.subscribe = null;
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['userId'])
|
||||
},
|
||||
|
||||
methods: {
|
||||
onAdd(data) {
|
||||
this.addData = Object.assign({}, this.addData, $A.isJson(data) ? data : {
|
||||
'userids': [this.userId],
|
||||
});
|
||||
this.addShow = true;
|
||||
},
|
||||
|
||||
onSubmit() {
|
||||
this.$refs.addForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.loadIng++;
|
||||
this.$store.dispatch("call", {
|
||||
url: 'users/agoraio/token',
|
||||
}).then(({data}) => {
|
||||
$A.loadScript('//download.agora.io/sdk/release/AgoraRTC_N.js', e => {
|
||||
if (e !== null || typeof AgoraRTC !== 'object') {
|
||||
this.loadIng--;
|
||||
$A.modalError("会议组件加载失败!");
|
||||
return;
|
||||
}
|
||||
this.join(data).then(_ => {
|
||||
this.loadIng--;
|
||||
this.addShow = false;
|
||||
this.meetingShow = true;
|
||||
})
|
||||
});
|
||||
}).catch(({msg}) => {
|
||||
this.loadIng--;
|
||||
$A.modalError(msg);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
onClose() {
|
||||
$A.modalConfirm({
|
||||
content: '确定要退出会议吗?',
|
||||
cancelText: '继续',
|
||||
okText: '退出',
|
||||
onOk: () => {
|
||||
this.loadIng++;
|
||||
this.leave().then(_ => {
|
||||
this.loadIng--;
|
||||
this.meetingShow = false;
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
join(options) {
|
||||
return new Promise(async resolve => {
|
||||
AgoraRTC.onAutoplayFailed = () => {
|
||||
// alert("click to start autoplay!")
|
||||
}
|
||||
AgoraRTC.onMicrophoneChanged = async (changedDevice) => {
|
||||
// When plugging in a device, switch to a device that is newly plugged in.
|
||||
if (changedDevice.state === "ACTIVE") {
|
||||
this.localTracks.audioTrack.setDevice(changedDevice.device.deviceId);
|
||||
// Switch to an existing device when the current device is unplugged.
|
||||
} else if (changedDevice.device.label === this.localTracks.audioTrack.getTrackLabel()) {
|
||||
const oldMicrophones = await AgoraRTC.getMicrophones();
|
||||
oldMicrophones[0] && this.localTracks.audioTrack.setDevice(oldMicrophones[0].deviceId);
|
||||
}
|
||||
}
|
||||
AgoraRTC.onCameraChanged = async (changedDevice) => {
|
||||
// When plugging in a device, switch to a device that is newly plugged in.
|
||||
if (changedDevice.state === "ACTIVE") {
|
||||
this.localTracks.videoTrack.setDevice(changedDevice.device.deviceId);
|
||||
// Switch to an existing device when the current device is unplugged.
|
||||
} else if (changedDevice.device.label === this.localTracks.videoTrack.getTrackLabel()) {
|
||||
const oldCameras = await AgoraRTC.getCameras();
|
||||
oldCameras[0] && this.localTracks.videoTrack.setDevice(oldCameras[0].deviceId);
|
||||
}
|
||||
}
|
||||
//
|
||||
this.agoraClient = AgoraRTC.createClient({
|
||||
mode: "rtc",
|
||||
codec: "vp8"
|
||||
});
|
||||
// Add an event listener to play remote tracks when remote user publishes.
|
||||
this.agoraClient.on("user-published", this.handleUserPublished);
|
||||
this.agoraClient.on("user-unpublished", this.handleUserUnpublished);
|
||||
// Join a channel and create local tracks. Best practice is to use Promise.all and run them concurrently.
|
||||
[options.uid, this.localTracks.audioTrack, this.localTracks.videoTrack] = await Promise.all([
|
||||
// Join the channel.
|
||||
this.agoraClient.join(options.appid, options.channel, options.token || null, options.uid || null),
|
||||
// Create tracks to the local microphone and camera.
|
||||
AgoraRTC.createMicrophoneAudioTrack(),
|
||||
AgoraRTC.createCameraVideoTrack()
|
||||
]);
|
||||
// Play the local video track to the local browser and update the UI with the user ID.
|
||||
this.localTracks.uid = options.uid;
|
||||
this.localTracks.mediaType = 'video';
|
||||
// Publish the local video and audio tracks to the channel.
|
||||
await this.agoraClient.publish([this.localTracks.audioTrack, this.localTracks.videoTrack]);
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
|
||||
leave() {
|
||||
return new Promise(async resolve => {
|
||||
for (let trackName in this.localTracks) {
|
||||
const track = this.localTracks[trackName];
|
||||
if (track) {
|
||||
if (['audioTrack', 'videoTrack'].includes(trackName)) {
|
||||
track.stop();
|
||||
track.close();
|
||||
}
|
||||
this.localTracks[trackName] = null;
|
||||
}
|
||||
}
|
||||
// Remove remote users and player views.
|
||||
this.remoteUsers = [];
|
||||
// leave the channel
|
||||
await this.agoraClient.leave();
|
||||
resolve();
|
||||
})
|
||||
},
|
||||
|
||||
async handleUserPublished(user, mediaType) {
|
||||
// subscribe to a remote user
|
||||
await this.agoraClient.subscribe(user, mediaType);
|
||||
// add remote
|
||||
user.mediaType = mediaType
|
||||
const index = this.remoteUsers.findIndex(item => item.uid == user.uid)
|
||||
if (index > -1) {
|
||||
this.remoteUsers.splice(index, 1, user)
|
||||
} else {
|
||||
this.remoteUsers.push(user)
|
||||
}
|
||||
},
|
||||
|
||||
handleUserUnpublished(user) {
|
||||
const index = this.remoteUsers.findIndex(item => item.uid == user.uid)
|
||||
if (index > -1) {
|
||||
this.remoteUsers.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="meeting-player">
|
||||
<div :id="id" class="player"></div>
|
||||
<UserAvatar :userid="player.uid" show-name/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "MeetingPlayer",
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: () => {
|
||||
return "meeting-player-" + Math.round(Math.random() * 10000);
|
||||
}
|
||||
},
|
||||
player: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
player: {
|
||||
handler(e) {
|
||||
this.$nextTick(_ => {
|
||||
switch (e.mediaType) {
|
||||
case 'video':
|
||||
e.videoTrack.play(this.id);
|
||||
break;
|
||||
case 'audio':
|
||||
e.audioTrack.play();
|
||||
break;
|
||||
}
|
||||
})
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -2,6 +2,7 @@
|
||||
@import "dialog-group-info";
|
||||
@import "dialog-wrapper";
|
||||
@import "file-content";
|
||||
@import "meeting-manager";
|
||||
@import "project-archived";
|
||||
@import "project-dialog";
|
||||
@import "project-gantt";
|
||||
|
||||
43
resources/assets/sass/pages/components/meeting-manager.scss
vendored
Normal file
43
resources/assets/sass/pages/components/meeting-manager.scss
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
body {
|
||||
.ivu-modal-wrap {
|
||||
&.meeting-manager {
|
||||
.ivu-modal {
|
||||
.ivu-modal-content {
|
||||
.ivu-modal-body {
|
||||
padding: 16px 12px 0;
|
||||
> ul {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
grid-template-columns: repeat(auto-fill, 180px);
|
||||
grid-gap: 16px;
|
||||
> li {
|
||||
list-style: none;
|
||||
width: 180px;
|
||||
height: 230px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.meeting-player {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.player {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
.common-avatar {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user