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

от автора

Меньше чем за год новая социальная сеть 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 для любой платформы готов!

ссылка на оригинал статьи https://habr.com/ru/company/Voximplant/blog/545090/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *