화상회의 기능을 직접 구현해본 개발자라면 얼마나 복잡한지 알 것이다. WebRTC 시그널링, 미디어 스트림 관리, 참여자 제어까지 손으로 하나하나 만들려면 몇 달이 걸린다. 다만 대부분의 백엔드 개발자는 Twilio Video 같은 전문 서비스가 있다는 걸 모르고 처음부터 바닥부터 만들려다가 프로젝트를 포기한다. 이번에는 Twilio Video API를 PHP에서 제대로 연동하는 방법을 정확히 정리해서 소개하겠다. 접근 토큰 생성부터 실제 화상 룸 생성, 참여자 관리까지 실전 코드로 보여줄 테니 참고하면 된다.

 

Twilio Video API란 뭔가

Twilio Video는 클라우드 기반 실시간 통신 플랫폼이다. 서버에서 토큰만 발급해주면, 클라이언트 측 JavaScript SDK가 알아서 피어 연결을 맺고 음성/비디오 스트림을 주고받는다. 너가 인프라를 구축할 필요가 없다. 대신 토큰 발급, 참여자 추적, 녹화 설정 같은 백엔드 로직은 직접 만들어야 한다.

Twilio Video의 핵심은 '접근 토큰'이다. 이 토큰 없이는 클라이언트가 룸에 접속할 수 없다. 토큰은 시간 제한이 있고(기본 1시간), 어떤 룸에, 어떤 권한으로 접속할지 정보가 포함되어 있다.

 

사전 준비: Twilio 계정과 SDK 설치

먼저 Twilio 계정을 만들고 Video API를 활성화해야 한다. https://www.twilio.com/console에 접속해서 API 키(API SID)와 API Secret을 얻는다. 대시보드의 '프로젝트 설정' 섹션에서 확인할 수 있다.

PHP에서 Twilio를 쓰려면 Composer로 공식 SDK를 설치한다.

composer require twilio/sdk

설치 후 autoload.php를 require하면 Twilio\\Rest\\Client 클래스를 쓸 수 있다.

 

1단계: 접근 토큰 생성하기

화상회의에 참여하려면 먼저 클라이언트에게 토큰을 발급해줘야 한다. 이 토큰은 참여자 신원, 참여할 룸 이름, 권한(음성 송수신, 비디오 송수신 여부)을 포함한다.

✗ 잘못된 방법: 토큰 검증 없이 생성

많은 개발자가 사용자 인증 없이 토큰을 발급한다. 이건 보안 위험이다. 누구나 화상회의에 접속할 수 있게 된다.

<?php
require 'vendor/autoload.php';

use Twilio\\Jwt\\AccessToken;
use Twilio\\Jwt\\Grants\\VideoGrant;

// 잘못된 예: 어떤 검증도 없음
$roomName = $_GET['room'];
$participantName = $_GET['name'];

$token = new AccessToken(
    getenv('TWILIO_ACCOUNT_SID'),
    getenv('TWILIO_API_KEY'),
    getenv('TWILIO_API_SECRET')
);

$videoGrant = new VideoGrant();
$videoGrant->setRoom($roomName);
$token->addGrant($videoGrant);
$token->setIdentity($participantName);

echo $token->toJWT();
?>

문제점: 누구든 roomName과 name을 전달하면 토큰을 받을 수 있다. 이건 무단 접속을 막을 수 없다.

✓ 올바른 방법: 인증 후 토큰 발급

사용자를 먼저 인증하고, 그 사용자가 해당 룸에 접속할 권한이 있는지 확인한 후 토큰을 발급한다.

<?php
require 'vendor/autoload.php';

use Twilio\\Jwt\\AccessToken;
use Twilio\\Jwt\\Grants\\VideoGrant;

session_start();

// 사용자 인증 확인
if (empty($_SESSION['user_id'])) {
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}

$roomName = $_POST['room_name'] ?? null;
$participantName = $_POST['participant_name'] ?? null;
$userId = $_SESSION['user_id'];

// 입력값 검증
if (!$roomName || !$participantName) {
    http_response_code(400);
    echo json_encode(['error' => 'room_name and participant_name required']);
    exit;
}

// 룸 접속 권한 확인 (데이터베이스에서 확인)
$db = new PDO('mysql:host=localhost;dbname=myapp', 'user', 'password');
$stmt = $db->prepare('SELECT id FROM conference_rooms WHERE room_name = ? AND user_id = ?');
$stmt->execute([$roomName, $userId]);

if (!$stmt->fetch()) {
    http_response_code(403);
    echo json_encode(['error' => 'Access denied to this room']);
    exit;
}

// 토큰 생성
$token = new AccessToken(
    getenv('TWILIO_ACCOUNT_SID'),
    getenv('TWILIO_API_KEY'),
    getenv('TWILIO_API_SECRET')
);

$videoGrant = new VideoGrant();
$videoGrant->setRoom($roomName);
$token->addGrant($videoGrant);
$token->setIdentity($userId . ':' . $participantName);
$token->setTimeToLive(3600); // 1시간

header('Content-Type: application/json');
echo json_encode([
    'token' => $token->toJWT(),
    'room_name' => $roomName
]);
?>

개선점: 세션에서 user_id를 확인하고, 데이터베이스에서 해당 사용자가 룸 접속 권한이 있는지 검증한다. 권한이 없으면 403 Forbidden을 반환한다.

 

2단계: 클라이언트에서 토큰으로 룸 접속

서버에서 발급한 토큰을 받으면, 클라이언트는 Twilio Video JavaScript SDK를 사용해서 룸에 접속한다. 백엔드에서 할 일은 아니지만, 전체 흐름을 이해하려면 클라이언트 코드도 알아야 한다.

<!DOCTYPE html>
<html>
<head>
    <title>Twilio Video Room</title>
</head>
<body>
    <div id="participants"></div>
    <script src="https://media.twiliocdn.com/sdk/js/video/releases/2.26.0/twilio-video.min.js"></script>
    <script>
        async function joinRoom() {
            // 서버에서 토큰 요청
            const response = await fetch('/get_token.php', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    room_name: 'my-room',
                    participant_name: 'John Doe'
                })
            });

            const data = await response.json();
            const { token, room_name } = data;

            // 룸에 접속
            const room = await Twilio.Video.connect(token, {
                name: room_name,
                audio: true,
                video: { width: 640 }
            });

            console.log('Room joined: ' + room.name);
            displayParticipants(room);
        }

        function displayParticipants(room) {
            room.participants.forEach(participantConnected);
            room.on('participantConnected', participantConnected);
            room.on('participantDisconnected', participantDisconnected);
        }

        function participantConnected(participant) {
            console.log('Participant joined: ' + participant.identity);
        }

        function participantDisconnected(participant) {
            console.log('Participant left: ' + participant.identity);
        }

        joinRoom();
    </script>
</body>
</html>

 

3단계: 서버에서 룸 상태 관리하기

Twilio 대시보드에서만 룸을 볼 수 있는 게 아니다. PHP 백엔드에서 Twilio REST API를 직접 호출해서 룸 정보를 조회하거나, 참여자를 강제 퇴장시키거나, 녹화를 시작할 수 있다.

활성 룸 목록 조회

현재 진행 중인 화상회의 룸을 확인할 수 있다.

<?php
require 'vendor/autoload.php';

use Twilio\\Rest\\Client;

$client = new Client(
    getenv('TWILIO_ACCOUNT_SID'),
    getenv('TWILIO_AUTH_TOKEN')
);

// 활성 룸 목록
$rooms = $client->video->v1->rooms->stream();

foreach ($rooms as $room) {
    echo "Room: " . $room->uniqueName . "\n";
    echo "Duration: " . $room->duration . " seconds\n";
    echo "Max Participants: " . $room->maxParticipants . "\n";
    echo "---\n";
}
?>
특정 룸의 참여자 조회

어떤 사용자들이 지금 룸에 있는지 확인한다.

<?php
require 'vendor/autoload.php';

use Twilio\\Rest\\Client;

$client = new Client(
    getenv('TWILIO_ACCOUNT_SID'),
    getenv('TWILIO_AUTH_TOKEN')
);

$roomSid = 'RM0123456789abcdef';

$participants = $client->video->v1->rooms($roomSid)
    ->participants
    ->stream();

foreach ($participants as $participant) {
    echo "Participant: " . $participant->identity . "\n";
    echo "Status: " . $participant->status . "\n";
    echo "Duration: " . $participant->duration . " seconds\n";
}
?>
특정 참여자를 룸에서 제거

금지된 사용자나 행동 문제가 있는 참여자를 강제 퇴장시킬 수 있다.

<?php
require 'vendor/autoload.php';

use Twilio\\Rest\\Client;

$client = new Client(
    getenv('TWILIO_ACCOUNT_SID'),
    getenv('TWILIO_AUTH_TOKEN')
);

$roomSid = 'RM0123456789abcdef';
$participantSid = 'PA0123456789abcdef';

// 참여자 제거
$client->video->v1->rooms($roomSid)
    ->participants($participantSid)
    ->update(array('status' => 'disconnected'));

echo "Participant removed from room";
?>

 

4단계: 녹화 설정 및 제어

화상회의를 자동 녹화할 수 있다. 룸을 만들 때 녹화 옵션을 설정하거나, 나중에 녹화를 시작/중지할 수 있다.

룸 생성 시 녹화 옵션 설정

새 룸을 만들 때 자동 녹화를 활성화하면, 참여자들이 들어오는 순간부터 녹화가 시작된다.

<?php
require 'vendor/autoload.php';

use Twilio\\Rest\\Client;

$client = new Client(
    getenv('TWILIO_ACCOUNT_SID'),
    getenv('TWILIO_AUTH_TOKEN')
);

$room = $client->video->v1->rooms->create(array(
    'uniqueName' => 'important-meeting-' . time(),
    'type' => 'go',
    'maxParticipants' => 10,
    'recordParticipantsOnConnect' => true,  // 자동 녹화
    'mediaRegion' => 'us1'  // 지역 설정(한국은 ap1, 미국은 us1)
));

echo json_encode([
    'room_sid' => $room->sid,
    'room_name' => $room->uniqueName,
    'recording_enabled' => true
]);
?>
녹화 파일 조회

이미 녹화된 파일을 조회하고 다운로드할 수 있다.

<?php
require 'vendor/autoload.php';

use Twilio\\Rest\\Client;

$client = new Client(
    getenv('TWILIO_ACCOUNT_SID'),
    getenv('TWILIO_AUTH_TOKEN')
);

$recordings = $client->video->v1->recordings->stream();

foreach ($recordings as $recording) {
    echo "Recording SID: " . $recording->sid . "\n";
    echo "Status: " . $recording->status . "\n";
    echo "Size: " . $recording->size . " bytes\n";
    
    if ($recording->status === 'completed') {
        echo "Download URL: " . $recording->links['download'] . "\n";
    }
    echo "---\n";
}
?>

 

주의사항과 흔한 실수

1. 토큰 만료 시간을 너무 길게 설정하지 마라

✗ 틀린 것: 24시간 유효한 토큰

$token->setTimeToLive(86400); // 24시간은 너무 길다

✓ 올바른 것: 1시간 또는 필요한 만큼만

$token->setTimeToLive(3600); // 1시간

토큰이 오래 유효하면, 누군가 토큰을 탈취했을 때 오랫동안 불법 접속할 수 있다. 화상회의 시간이 1시간을 초과하면, 중간에 새 토큰을 발급받게 설계하는 게 낫다.

2. API Key와 Auth Token을 헷갈리지 마라

Twilio에는 두 가지 인증 방식이 있다. 토큰 생성할 때는 API Key와 API Secret을 쓰고, REST API 호출할 때는 Account SID와 Auth Token을 쓴다. 섞어서 쓰면 인증 실패가 난다.

용도 필요한 것
클라이언트 접근 토큰 생성 Account SID + API Key + API Secret
REST API 호출(룸 관리 등) Account SID + Auth Token

3. 룸 SID와 룸 이름을 혼동하지 마라

룸을 만들면 uniqueName(사람이 읽을 수 있는 이름)과 SID(Twilio 시스템 ID)가 생긴다. 클라이언트 접속할 때는 uniqueName을 쓰고, REST API에서 룸을 조회하거나 참여자를 제거할 때는 SID를 써야 한다.

4. 활성 룸이 자동 삭제되지 않는다

마지막 참여자가 나가도 룸이 남아있을 수 있다. 시간이 지나면 자동 삭제되지만, 수동으로 삭제하려면 REST API를 호출해야 한다.

$client->video->v1->rooms($roomSid)->update(array('status' => 'completed'));

 

정리: Twilio Video는 복잡한 화상회의 기능을 즉시 사용 가능하게 만들어준다

Twilio Video API를 쓰면, 수개월이 걸릴 화상회의 시스템을 며칠 안에 만들 수 있다. 접근 토큰을 올바르게 생성하고, 클라이언트에 전달하고, 서버에서 룸과 참여자를 관리하는 세 단계만 잘 이해하면 된다. 이 글의 인증 검증 코드와 룸 관리 예제를 참고해서 구현하면, 실제 서비스에 바로 적용할 수 있을 것이다.