устанавливается на чистый дистрибутив Ubuntu 14.04 x64. Прочие дистрибутивы не поддерживаются.
Первым делом - sudo apt-get update и sudo apt-get upgrade. Пакеты в системе обновляются до последних версий.
Добавляется репозиторий bbb:
wget http://ubuntu.bigbluebutton.org/bigbluebutton.asc -O- | sudo apt-key add -
echo
"deb http://ubuntu.bigbluebutton.org/trusty-090/ bigbluebutton-trusty
main" | sudo tee /etc/apt/sources.list.d/bigbluebutton.list
Устанавливается ffmpeg: я создаю в своей домашней папке файл ffmpeg.sh (touch ~/ffmpeg.sh
), командой chmod +x ~/ffmpeg.sh
делаю его исполняемым и запускаю: ~/ffmpeg.sh
. Содержимое файла:
sudo apt-get install build-essential git-core checkinstall yasm texi2html libvorbis-dev libx11-dev libvpx-dev libxfixes-dev zlib1g-dev pkg-config netcat FFMPEG_VERSION=2.3.3 cd /usr/local/src if [ ! -d "/usr/local/src/ffmpeg-${FFMPEG_VERSION}" ]; then sudo wget "http://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2" sudo tar -xjf "ffmpeg-${FFMPEG_VERSION}.tar.bz2" fi cd "ffmpeg-${FFMPEG_VERSION}" sudo ./configure --enable-version3 --enable-postproc --enable-libvorbis --enable-libvpx sudo make sudo checkinstall --pkgname=ffmpeg --pkgversion="5:${FFMPEG_VERSION}" --backup=no --deldoc=yes --default
Снова sudo apt-get update
.
Непосредственно установка bbb:
sudo apt-get install bigbluebutton
sudo bbb-conf --clean
sudo bbb-conf --check
Далее - установка apache2 и php5. В Ubuntu 14.04 на данный момент по умолчанию ставятся php5.5 и apache2.4. Более новые версии можно получить из сторонних репозиториев, но те, что есть, еще долгое время будут поддерживаться. Устанавливается всё одной командой (апач и модули к нему подтягиваются сами):
sudo apt-get install php5
80 порт занят nginx, поэтому, если bbb и оболочка к нему будут
размещаться на одном сервере, апач нужно перенастроить в файле
/etc/apache2/ports.conf. После того как открыты посты и настроены
сервера, необходимо переназначить заменить, по которому доступен bbb, на
доменное имя: sudo bbb-conf --setip webinar.implant.ru
. Эта команда перезагрузит все сервисы и отредактирует все конфиги самостоятельно.
Само собой, bbb "не знает", как и какими средствами к его api будут
обращаться, поэтому перенаправление запросов с nginx на apache нужно
обеспечить самостоятельно. В файл /etc/nginx/sites-enabled/bigbluebutton-default
добавим такую секцию:
location ~* ^/.+\.(php)$ { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Host $host; proxy_pass http://127.0.0.1:81; }
Поскольку с доступом к webinar.implant.ru из нашей сети проблемы,
для себя я решил вопрос строкой в файле hosts на рабочем компьютере: 10.10.10.66 webinar.implant.ru
.
Этого мало: сайт будет открываться, но не будет работать, т. к. сервер
тоже не может обратиться к самому себе ни по домену, ни по внешнему ip.
Вопрос решается похожей строчкой в /etc/hosts
: 127.0.0.1 webinar.implant.ru
.
https://code.google.com/p/bigbluebutton/wiki/API
-
официальная документация. Я при беглом чтении не нашел в ней инструкций о
том, как именно формируется контрольная сумма для каждого метода (у
многих методов разные наборы параметров, а у некоторых их нет и вовсе),
поэтому прикладываю библиотеку с примерами использования.
Конфигурация внешнего вида и прочих тонкостей лежит тут:
/var/www/bigbluebutton/client/conf/config.xml
- общие внешний вид и настройка каждого молуля. Можно заставить веб-камеры зрителей автоматически включаться, например./var/www/bigbluebutton/client/conf/layout.xml
- настройки внешнего виде. Можно настроить несколько схем и быстро
переключаться между ними во время вебинара. Можно настроить схемы так,
чтобы переключаться между ними мог только лектор./var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
- всё остальное, в т. ч. настройки создаваемых вебинаров по умолчанию.
<?php
namespace app\models;
use Yii;
use yii\base\Model;
use app\models\CreateForm;
class BigBlueButton extends Model {
private $_securitySalt;
private $_bbbServerBaseUrl;
private $_meetingId;
private $_meetingName;
private $_username;
private $_password;
function __construct() {
$this->_securitySalt = Yii::$app->params['bbbSalt'];
$this->_bbbServerBaseUrl = Yii::$app->params['bbbHost'];
}
private function _processXmlResponse($url, $xml = '') {
if (extension_loaded('curl')) {
$ch = curl_init() or die (curl_error());
$timeout = 10;
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
if (!empty($xml)) {
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-type: application/xml',
'Content-length: ' . strlen($xml)
));
}
$data = curl_exec($ch);
curl_close($ch);
if ($data) {
return new \SimpleXMLElement($data);
} else {
return false;
}
}
if (!empty($xml)) {
throw new Exception('Set xml, but curl does not installed.');
}
return simplexml_load_file($url);
}
private function _requiredParam($param, $name = '') {
if ((isset($param)) && ($param != '')) {
return $param;
} elseif (!isset($param)) {
throw new Exception('Missing parameter.');
} else {
throw new Exception($name . ' is required.');
}
}
/* private function _optionalParam($param) {
if ((isset($param)) && ($param != '')) {
return $param;
} else {
return $param = '';
}
}*/
public function getCreateMeetingUrl($creationParams) {
$this->_meetingId = $this->_requiredParam($creationParams['meetingId'], 'meetingId');
$this->_meetingName = $this->_requiredParam($creationParams['meetingName'], 'meetingName');
$creationUrl = $this->_bbbServerBaseUrl . "api/create?";
$params = 'name=' . urlencode($this->_meetingName) .
'&meetingID=' . urlencode($this->_meetingId) .
'&attendeePW=' . urlencode($creationParams['attendeePw']) .
'&moderatorPW=' . urlencode($creationParams['moderatorPw']) .
'&dialNumber=' . urlencode($creationParams['dialNumber']) .
'&voiceBridge=' . urlencode($creationParams['voiceBridge']) .
'&webVoice=' . urlencode($creationParams['webVoice']) .
'&logoutURL=' . urlencode($creationParams['logoutUrl']) .
'&maxParticipants=' . urlencode($creationParams['maxParticipants']) .
'&record=' . urlencode($creationParams['record']) .
'&duration=' . urlencode($creationParams['duration']);
$welcomeMessage = $creationParams['welcomeMsg'];
if (trim($welcomeMessage)) {
$params .= '&welcome=' . urlencode($welcomeMessage);
}
return $creationUrl . $params . '&checksum=' . sha1("create" . $params . $this->_securitySalt);
}
public function createMeetingWithXmlResponseArray($creationParams, $xml = '') {
$xml = $this->_processXmlResponse($this->getCreateMeetingURL($creationParams), $xml);
if ($xml) {
if ($xml->meetingID) {
return [
'returncode' => $xml->returncode,
'message' => $xml->message,
'messageKey' => $xml->messageKey,
'meetingId' => $xml->meetingID,
'meetingName' => $xml->meetingName,
'attendeePw' => $xml->attendeePW,
'moderatorPw' => $xml->moderatorPW,
'hasBeenForciblyEnded' => $xml->hasBeenForciblyEnded,
'createTime' => $xml->createTime
];
} else {
return [
'returncode' => $xml->returncode,
'message' => $xml->message,
'messageKey' => $xml->messageKey
];
}
} else {
return null;
}
}
public function getJoinMeetingURL($joinParams) {
$this->_meetingId = $this->_requiredParam($joinParams['meetingId'], 'meetingId');
$this->_username = $this->_requiredParam($joinParams['username'], 'username');
$this->_password = $this->_requiredParam($joinParams['password'], 'password');
$joinUrl = $this->_bbbServerBaseUrl . "api/join?";
$params = 'meetingID=' . urlencode($this->_meetingId) .
'&fullName=' . urlencode($this->_username) .
'&password=' . urlencode($this->_password) .
'&userID=' . urlencode($joinParams['userId']) .
'&webVoiceConf=' . urlencode($joinParams['webVoiceConf']);
if (((isset($joinParams['createTime'])) && ($joinParams['createTime'] != ''))) {
$params .= '&createTime=' . urlencode($joinParams['createTime']);
}
return $joinUrl . $params . '&checksum=' . sha1('join' . $params . $this->_securitySalt);
}
public function getEndMeetingURL($endParams) {
$this->_meetingId = $this->_requiredParam($endParams['meetingId'], 'meetingId');
$this->_password = $this->_requiredParam($endParams['password'], 'password');
$endUrl = $this->_bbbServerBaseUrl . 'api/end?';
$params = 'meetingID=' . urlencode($this->_meetingId) . '&password=' . urlencode($this->_password);
return $endUrl . $params . '&checksum=' . sha1('end' . $params . $this->_securitySalt);
}
public function endMeetingWithXmlResponseArray($endParams) {
$xml = $this->_processXmlResponse($this->getEndMeetingURL($endParams));
if ($xml) {
return [
'returncode' => $xml->returncode,
'message' => $xml->message,
'messageKey' => $xml->messageKey
];
} else {
return null;
}
}
public function getIsMeetingRunningUrl($meetingId) {
$this->_meetingId = $this->_requiredParam($meetingId, 'meetingId');
$runningUrl = $this->_bbbServerBaseUrl . 'api/isMeetingRunning?';
$params = 'meetingID=' . urlencode($this->_meetingId);
return $runningUrl . $params . '&checksum=' . sha1('isMeetingRunning' . $params . $this->_securitySalt);
}
public function isMeetingRunningWithXmlResponseArray($meetingId) {
$xml = $this->_processXmlResponse($this->getIsMeetingRunningUrl($meetingId));
if ($xml) {
return [
'returncode' => $xml->returncode,
'running' => $xml->running
];
} else {
return null;
}
}
public function getGetMeetingsUrl() {
return $getMeetingsUrl = $this->_bbbServerBaseUrl . 'api/getMeetings?checksum=' . sha1('getMeetings' . $this->_securitySalt);
}
public function getMeetingsWithXmlResponseArray() {
$xml = $this->_processXmlResponse($this->getGetMeetingsUrl());
if ($xml) {
if ($xml->returncode != 'SUCCESS') {
$result = [
'returncode' => $xml->returncode
];
return $result;
} elseif ($xml->messageKey == 'noMeetings') {
$result = [
'returncode' => $xml->returncode,
'messageKey' => $xml->messageKey,
'message' => $xml->message
];
return $result;
} else {
$result = [
'returncode' => $xml->returncode,
'messageKey' => $xml->messageKey,
'message' => $xml->message
];
foreach ($xml->meetings->meeting as $m) {
$result[] = array(
'meetingId' => $m->meetingID,
'meetingName' => $m->meetingName,
'createTime' => $m->createTime,
'attendeePw' => $m->attendeePW,
'moderatorPw' => $m->moderatorPW,
'hasBeenForciblyEnded' => $m->hasBeenForciblyEnded,
'running' => $m->running
);
}
return $result;
}
} else {
return null;
}
}
public function getMeetingInfoUrl($infoParams) {
$this->_meetingId = $this->_requiredParam($infoParams['meetingId'], 'meetingId');
$this->_password = $this->_requiredParam($infoParams['password'], 'password');
$infoUrl = $this->_bbbServerBaseUrl . 'api/getMeetingInfo?';
$params = 'meetingID=' . urlencode($this->_meetingId) . '&password=' . urlencode($this->_password);
return $infoUrl . $params . '&checksum=' . sha1('getMeetingInfo' . $params . $this->_securitySalt);
}
public function getMeetingInfoWithXmlResponseArray($infoParams) {
$xml = $this->_processXmlResponse($this->getMeetingInfoUrl($infoParams));
if ($xml) {
if (($xml->returncode != 'SUCCESS') || ($xml->messageKey == null)) {
$result = [
'returncode' => $xml->returncode,
'messageKey' => $xml->messageKey,
'message' => $xml->message
];
return $result;
} else {
$result = [
'returncode' => $xml->returncode,
'meetingName' => $xml->meetingName,
'meetingId' => $xml->meetingID,
'createTime' => $xml->createTime,
'voiceBridge' => $xml->voiceBridge,
'attendeePw' => $xml->attendeePW,
'moderatorPw' => $xml->moderatorPW,
'running' => $xml->running,
'recording' => $xml->recording,
'hasBeenForciblyEnded' => $xml->hasBeenForciblyEnded,
'startTime' => $xml->startTime,
'endTime' => $xml->endTime,
'participantCount' => $xml->participantCount,
'maxUsers' => $xml->maxUsers,
'moderatorCount' => $xml->moderatorCount,
];
foreach ($xml->attendees->attendee as $a) {
$result[] = [
'userId' => $a->userID,
'fullName' => $a->fullName,
'role' => $a->role
];
}
return $result;
}
} else {
return null;
}
}
public function getRecordingsUrl($recordingParams) {
$recordingsUrl = $this->_bbbServerBaseUrl . "api/getRecordings?";
$params = 'meetingID=' . urlencode($recordingParams['meetingId']);
return ($recordingsUrl . $params . '&checksum=' . sha1('getRecordings' . $params . $this->_securitySalt));
}
public function getRecordingsWithXmlResponseArray($recordingParams) {
$xml = $this->_processXmlResponse($this->getRecordingsUrl($recordingParams));
if ($xml) {
if (($xml->returncode != 'SUCCESS') || ($xml->messageKey == null)) {
$result = array(
'returncode' => $xml->returncode,
'messageKey' => $xml->messageKey,
'message' => $xml->message
);
return $result;
} else {
$result = array(
'returncode' => $xml->returncode,
'messageKey' => $xml->messageKey,
'message' => $xml->message
);
foreach ($xml->recordings->recording as $r) {
$result[] = [
'recordId' => $r->recordID,
'meetingId' => $r->meetingID,
'name' => $r->name,
'published' => $r->published,
'startTime' => $r->startTime,
'endTime' => $r->endTime,
'playbackFormatType' => $r->playback->format->type,
'playbackFormatUrl' => $r->playback->format->url,
'playbackFormatLength' => $r->playback->format->length,
'metadataTitle' => $r->metadata->title,
'metadataSubject' => $r->metadata->subject,
'metadataDescription' => $r->metadata->description,
'metadataCreator' => $r->metadata->creator,
'metadataContributor' => $r->metadata->contributor,
'metadataLanguage' => $r->metadata->language,
];
}
return $result;
}
} else {
return null;
}
}
public function getPublishRecordingsUrl($recordingParams) {
$recordingsUrl = $this->_bbbServerBaseUrl . 'api/publishRecordings?';
$params = 'recordID=' . urlencode($recordingParams['recordId']) . '&publish=' . urlencode($recordingParams['publish']);
return $recordingsUrl . $params . '&checksum=' . sha1('publishRecordings' . $params . $this->_securitySalt);
}
public function publishRecordingsWithXmlResponseArray($recordingParams) {
$xml = $this->_processXmlResponse($this->getPublishRecordingsUrl($recordingParams));
if ($xml) {
return [
'returncode' => $xml->returncode,
'published' => $xml->published
];
} else {
return null;
}
}
public function getDeleteRecordingsUrl($recordingParams) {
$recordingsUrl = $this->_bbbServerBaseUrl . 'api/deleteRecordings?';
$params = 'recordID=' . urlencode($recordingParams['recordId']);
return $recordingsUrl . $params . '&checksum=' . sha1('deleteRecordings' . $params . $this->_securitySalt);
}
public function deleteRecordingsWithXmlResponseArray($recordingParams) {
$xml = $this->_processXmlResponse($this->getDeleteRecordingsUrl($recordingParams));
if ($xml) {
return array(
'returncode' => $xml->returncode,
'deleted' => $xml->deleted
);
} else {
return null;
}
}
}