Pull to refresh
144.79
Rating
Voximplant
Облачная платформа голосовой и видеотелефонии

Clubhouse своими руками: для iOS, Android, Web и даже Unity

Voximplant corporate blogProgrammingDevelopment of communication systemsConferencesSocial networks and communities
Tutorial

Меньше чем за год новая социальная сеть Clubhouse набрала больше 6 миллионов участников — и всё продолжает расти. В чём же секрет такой популярности?

Clubhouse — принципиально новый формат социальных сетей, такого ещё не было. Во время пандемии людям стало не хватать живого общения, поэтому в Clubhouse всё общение происходит голосом, через так называемые комнаты. Никаких привычных нам сообщений и диалогов. Всё общение происходит в реальном времени.

Комнаты могут быть закрытыми “для своих” или открытыми для всех желающих. Создатель (модератор) комнаты сам выбирает, кто может говорить, а присоединившиеся участники могут поднять руку, чтобы выразить желание что-то сказать, и если модератор захочет, включит желающему микрофон.

Однако попасть в Clubhouse могут не все. В настоящий момент официальное приложение доступно лишь владельцам айфонов, а зарегистрироваться можно только по приглашению от уже зарегистрированного пользователя. Но что, если у меня андроид? Или я хочу сидеть в соцсетях с компьютера?

Можно, конечно, терпеливо ждать, когда они решат выпустить версию для андроидов и десктопную версию, а можно поступить креативно – написать свой Clubhouse, с блэкджеком записью разговоров и поддержкой любых платформ. А Voximplant поможет с этим.

В этой статье я покажу, как можно создать комнату-конференцию наподобие Clubhouse для Web SDK, в качестве клиента я буду использовать браузер.

Обратите внимание, что Voximplant поддерживает множество SDK, начиная с Web, iOS, Android и заканчивая Flutter, React Native и даже Unity. То есть если вы пишете игру на Unity, есть возможность встроить аудио- или даже видеочат прямо в неё.

Настройки VoxEngine

Создаём комнату. Заходим в свой аккаунт Voximplant (или регистрируем новый) и создаём там приложение, нового пользователя для авторизации и начинаем писать сценарий.

Сначала подключим модуль конференций Voximplant и обработаем отключение участников:

require(Modules.Conference)
let owner = null;
const onEndpointDisconnected = (event)=>{
   const members = conference.getList();
   if(members.length === 1){
       VoxEngine.terminate();
       return;
   }
}

Затем обработаем проверку разрешений:

const checkPermissions = ({call,headers}) =>{
   return new Promise((resolve)=>{
       setTimeout(()=>{resolve(true)},500);
   });
}

Создание новой конференции и подключение нового участника к комнате:

let logURL = ''; // for debug reason
let conference = null;
VoxEngine.addEventListener(AppEvents.Started, event => {
   logURL = event.logURL;
   conference = VoxEngine.createConference({hd_audio:true});
})
VoxEngine.addEventListener(AppEvents.CallAlerting, async event => {
   const permissions = await checkPermissions(event);
   if(permissions) {
       event.call.addEventListener(CallEvents.Disconnected, onEndpointDisconnected);
       event.call.answer();
       conference.add({
           call: event.call,
           displayName: event.headers['X-Name'],
           mode: "FORWARD",
           direction: "BOTH",
           scheme: event.scheme
       });
       if(conference.getList().length === 1){
           owner = conference.getList()[0].id();
           conference.getList()[0].getCall().sendMessage('owner');
       }
       conference.get(owner).getCall()
       .sendMessage(conference.getList().length);)
   } else {
       event.call.hangup({'X-Reason':'DENIED'});
   }
});

На этом с настройкой VoxEngine всё.

Настройка клиента

Далее перейдём к клиентской части. Мы используем Web SDK, так что клиент будет представлять собой HTML-страницу. Кнопки управления конференцией скроем до инициализации SDK:

<!DOCTYPE html>
<html lang="en">
<head>
   <style>
       .hidden {
           display: none !important;
       }
   </style>
   <meta charset="UTF-8">
   <title>The demo</title>
</head>
<body>
<div id="btns" class="hidden">
   <p id="myname">Avi</p>
   <button id="viewer">Join as listener</button>
   <button id="speaker">Join as speaker</button>
   <button id="leave" disabled>Leave</button>
</div>
<div id="audio"></div>
<h3>Current speakers <span id="countSpeakers">0</span></h3>
<div id="endpoints"></div>
</body>
<script src="*****"></script>

Далее самое интересное. Пишем сценарий в теге <script>. Для начала инициализируем наш SDK, авторизуемся и отобразим кнопки:

<script>
// sdk init
const sdk = VoxImplant.getInstance();
let user = 'user*****';
const init = async () => {
   await sdk.init({ showDebugInfo: true, serverIp: 'url*****' });
   await sdk.connect();
   await sdk.login(`${user}@app**.acc**.voximplant.com`, 'pass*****');
}
init().then(() => {
   document.getElementById('btns').classList.remove('hidden');
});

Задаём нужные константы для конференции. Имя пользователя для участника комнаты я возьму из тега с id=”myname”. Затем определим, когда видны определённые кнопки:

let currentCall;
let currentRole;
let countSpeakers = 0;
const confNumber = 'Test room';
const speakerBtn = document.getElementById('speaker');
const viewerBtn = document.getElementById('viewer');
const leaveBtn = document.getElementById('leave');
document.getElementById('myname').innerText = myName;
let setRole = (role) => {
   currentRole = role;
   if(role === 'speaker') {
       speakerBtn.disabled = true;
       leaveBtn.disabled = false;
       viewerBtn.disabled = false;
   }
   if(role === 'viewer') {
       speakerBtn.disabled = false;
       leaveBtn.disabled = false;
       viewerBtn.disabled = true;
   }
   if(role === 'start') {
       speakerBtn.disabled = false;
       viewerBtn.disabled = false;
       leaveBtn.disabled = true;
   }
}

Обработаем завершение конференции:

let endCall = () => {
   if(currentCall && currentCall.state() !== 'ENDED') {
       document.getElementById('endpoints').innerText = '';
       currentCall.hangup();
       setRole('start');
   }
}

Обработаем добавление нового участника в комнату и удаление из нее:

let onEndpointAdded = (e) => {
   console.warn('Endpoint added', e.endpoint.id);
   const nameTable = document.getElementById('endpoints');
   let p = document.createElement('p');
   p.id = e.endpoint.id;
   p.innerText = `Name: ${e.endpoint.displayName}, id: ${e.endpoint.id}`;
   nameTable.append(p);
   document.getElementById('countSpeakers').innerText = countSpeakers + 1;
   e.endpoint.addEventListener(VoxImplant.EndpointEvents.RemoteMediaAdded, (ev)=> {
       console.warn('RemoteMediaAdded', ev.mediaRenderer);
       const nodeCall = document.getElementById('audio');
       ev.mediaRenderer.render(nodeCall);
   })
   e.endpoint.addEventListener(VoxImplant.EndpointEvents.RemoteMediaRemoved, (ev)=>{
       console.warn(`Endpoint ${e.endpoint.id} media removed ${ev.mediaRenderer}`);
   })
   // ENDPOINT REMOVED
   e.endpoint.addEventListener(VoxImplant.EndpointEvents.Removed, (ev)=>{
       console.warn(`Endpoint ${e.endpoint.id} removed`);
       let removeP = document.getElementById(e.endpoint.id);
       nameTable.removeChild(removeP);
   })
}

Обрабатываем действие по кнопке выхода из комнаты и другие события:

const setCall = () => {
   leaveBtn.onclick = endCall;
   currentCall.addEventListener(VoxImplant.CallEvents.EndpointAdded, onEndpointAdded);
   currentCall.addEventListener(VoxImplant.CallEvents.MessageReceived, (e) => {
       console.warn('MessageReceived', e.text);
   });
   //handle connection
   currentCall.addEventListener(VoxImplant.CallEvents.Connected, () => {
       console.warn(`Call connected successfully`);
   });
   //other call event listeners
   currentCall.addEventListener(VoxImplant.CallEvents.Disconnected, () => {
       console.warn(`Call disconnected`);
       endCall();
   });
   currentCall.addEventListener(VoxImplant.CallEvents.Failed, (e) => {
       console.warn(`Call failed`);
       endCall();
   });
}

И напоследок обработаем кнопки, которые задают роль участника комнаты (говорящий или слушатель):

speakerBtn.onclick = async () => {
   document.getElementById('endpoints').innerText = '';
   if(currentCall) {
       document.getElementById('endpoints').innerText = '';
       await currentCall.hangup();
   }
   setTimeout(() => {
       setRole('speaker');
       currentCall = sdk.callConference({
           number: confNumber,
           extraHeaders: {'X-Name': myName}
       });
       setCall();
   }, 300)
}
viewerBtn.onclick = async () => {
   if(currentCall) {
       document.getElementById('endpoints').innerText = '';
       await currentCall.hangup();
   }
   setTimeout(() => {
       setRole('viewer');
       currentCall = sdk.joinAsViewer(confNumber);
       setCall();
   }, 300)
}
</script>
</html>

Готово. На выходе получаем комнату-аудиоконференцию, в которой участники могут быть говорящими или просто слушателями, управление ролями происходит с помощью кнопок, а ниже виден список говорящих с их именами и ID.

Остаётся только сделать базу данных пользователей и комнат, красивый выделяющийся интерфейс — и ваш собственный конкурент Clubhouse для любой платформы готов!

Tags:аудиоконференциисоцсетиclubhouseпрограммированиесоздание мобильных приложенийсоздание веб-сайтовсоциальные сети
Hubs: Voximplant corporate blog Programming Development of communication systems Conferences Social networks and communities
Total votes 22: ↑20 and ↓2 +18
Views5.6K

Comments 14

Only those users with full accounts are able to leave comments. Log in, please.

Top of the last 24 hours

Information

Founded
Location
Россия
Website
www.voximplant.com
Employees
101–200 employees
Registered