안전한 온라인 시험 플랫폼 구현: RTC 엔진을 활용한 개발자 가이드

10 분 읽기
Feb 18, 2025

시나리오 개요

온라인 시험은 네트워크를 통해 컴퓨터를 운영하여 실시되는 시험의 한 형태로, 종이 매체와는 분리되어 있으며 온라인 매체를 통해 실시되는 시험으로 설명될 수 있습니다.

  • 온라인 시험에는 국가 컴퓨터 순위 시험, 대학 영어 시험, 대학원 입학 시험, 직업 시험, 금융 시험, 공학 시험, 외환 무역 시험, 전문대학에서 학부로의 승급 시험, 공무원 시험 및 기업 서면 시험과 같은 다양한 무종이 시험 시스템이 포함됩니다. 시험이 완료된 후 자동 채점과 같은 작업을 수행하여 최종 성적 보고서를 얻을 수 있으며, 이는 저장을 위해 이메일로 전송되거나 온라인으로 인쇄될 수 있습니다.
  • 전통적인 시험은 문제 설정, 시험지 작성 및 인쇄, 시험지 배포, 답안 제출, 시험지 수집 및 채점, 결과 발표 및 시험 결과 통계 분석 등 모든 과정에 수동적으로 참여해야 합니다. 이 과정은 길고 노동 집약적이며 오류가 발생하기 쉬우며 적절한 기밀 유지 작업이 필요하여 전체 학습 및 시험 비용이 높아집니다. 온라인 시험 학습 시스템은 완전히 종이 없는 네트워크화되고 자동화된 컴퓨터 온라인 학습 및 시험을 달성할 수 있으며, 이는 단위의 정보화 구축에 깊은 실용적 의미와 가치를 지닙니다.

온라인 시험과 오프라인 컴퓨터 기반 시험은 동일한 비즈니스 모델이 아님을 유의해야 합니다. 오프라인 컴퓨터 기반 시험은 오프라인 IDC에서 온라인 시스템을 통해 시험을 수행하는 것을 말하며, 전통적인 시험과의 차이는 시험 문제가 종이에서 완료되는지 또는 컴퓨터 소프트웨어에서 완료되는지에만 해당됩니다.

구현 계획

온라인 시험 장면에 대한 전체 참조 비즈니스 프로세스는 그림에 표시되어 있습니다. 온라인 시험 시스템의 사용자는 일반적으로 수험생, 감독관, 시험 관리자 및 문제 출제자(채점자)를 포함합니다.

  • 수험생: 수험생은 수험생 계정에 로그인하여 시험실에 들어가야 하며, 카메라 및 화면 공유를 활성화하고 모니터링된 조건에서 시험지를 완료하고 제출해야 합니다.
  • 감독관: 감독관은 감독관 계정에 로그인해야 하며, 이를 통해 시험에 참여하는 모든 수험생의 카메라 피드를 볼 수 있어 부정행위를 확인할 수 있습니다. 시스템 자체에도 시험 애플리케이션이 전환된 횟수와 시간 등을 모니터링하는 일부 자동 모니터링 기능이 있습니다. 일부 상황에서는 허용된 범위 내에서 수험생의 질문에 답변해야 할 수도 있습니다.
  • 시험 관리자: 그들은 시험실을 생성하고, 해당 시험실에 시험 문제를 업로드하며, 시험실 접근을 제어해야 합니다. 일반적으로 감독관 중 한 명이 이 역할을 수행할 수도 있습니다.
  • 교사: 그들의 주요 업무는 시험 전에 문제를 설정하고 시험 후 시험지를 수동으로 채점하는 것입니다(필요한 경우).

온라인 시험 비즈니스 흐름도:

온라인 시험 비즈니스 흐름도: - 학생: 시험 전 검토, 학생 끝에 로그인, 장비 점검 완료, 시험실에 들어가기

비즈니스 프로세스

이 문서는 전체 시나리오의 구현 흐름을 더 잘 이해할 수 있도록 돕기 위해 일부 일반적인 비즈니스 프로세스를 요약합니다.

  • 로그인 및 로그아웃
로그인 및 로그아웃 흐름도 1. 시작 2. 비즈니스 백엔드에서 userSig 가져오기 3. 채팅 초기화 4. 채팅 이벤트 리스닝 콜백 설정
  • 방 생성/파괴
  • 오디오 및 비디오 스트리밍
  • 오디오 및 비디오 풀 스트리밍

적용된 제품

RTC 엔진 & 채팅

기본 통합 가이드라인

온라인 시험의 원활한 진행을 보장하기 위해 사전에 콘솔을 통해 티켓을 제출하고 시험 응시자 수, 인원 지역 분포, 해상도, 비트 전송률 및 기타 사용 정보를 등록하는 것이 좋습니다.

서비스 활성화

음성 채팅방 시나리오는 일반적으로 클라우드 플랫폼의 두 가지 유료 PaaS 서비스인 채팅실시간 통신 (TRTC) 에 의존합니다. 대화형 화이트보드는 선택 사항이며 선택하거나 자체 호스팅할 수 있습니다.

1. 콘솔에 로그인하여 응용 프로그램을 생성해야 합니다. RTC 엔진 및 채팅 서비스를 선택하십시오. 이때 SDKAppID는 콘솔에서 자동으로 생성됩니다. 두 서비스의 계정 및 인증 시스템은 재사용 가능합니다. 이후 필요에 따라 RTC 또는 채팅 응용 프로그램 버전을 업그레이드할 수 있습니다. 예를 들어, 고급 버전을 선택하면 더 많은 부가 가치 기능 및 서비스를 잠금 해제할 수 있습니다.

참고:

  • 테스트 환경과 생산 환경을 위해 두 개의 별도 응용 프로그램을 만드는 것이 좋습니다. 각 계정(UIN)은 1년 내에 월 10,000분의 무료 사용 시간을 제공합니다.
  • TRTC 월 패키지는 체험판, 기본판 및 전문판으로 나뉘며, 각기 다른 부가 가치 기능 및 서비스를 잠금 해제할 수 있습니다. 자세한 내용은 버전 기능 및 월 패키지 설명.

2. 응용 프로그램이 생성되면 응용 프로그램 관리 - 응용 프로그램 개요 섹션에서 해당 응용 프로그램의 기본 정보를 찾을 수 있습니다. SDKAppIDSDKSecretKey를 안전하게 저장하여 향후 사용하고 무단 트래픽 사용을 방지하는 것이 중요합니다.

SDK 가져오기

TRTC SDK채팅 SDK의 압축 파일을 로컬 머신에 다운로드하고 추출합니다. 프로젝트의 루트 디렉터리에 thirdparty라는 디렉터리를 만들어 모든 SDK를 저장할 수 있습니다. TRTC SDK 및 채팅 SDK를 thirdparty 디렉터리로 이동하여 향후 사용합니다.

1. QTCreator와 통합

// *.pro
INCLUDEPATH +=  $$PWD/thirdparty/TRTC_SDK/CPlusPlus/Win64/include \
              $$PWD/thirdparty/TRTC_SDK/CPlusPlus/Win64/include/TRTC \
              $$PWD/thirdparty/IM_SDK/include

LIBS += -L'$$PWD/thirdparty/TRTC_SDK/CPlusPlus/Win64/lib' -lliteav \
        -L'$$PWD/thirdparty/IM_SDK/lib/Win64' -lImSDK

2. Visual Studio와 통합

  • Visual Studio에서 헤더 파일 디렉터리를 추가하려면 구성 속성 > C/C++ > 일반 > 추가 헤더 디렉터리로 이동하여 헤더 파일 경로를 추가합니다.
$(SolutionDir)thirdparty/TRTC_SDK/CPlusPlus/Win64/include
$(SolutionDir)thirdparty/TRTC_SDK/CPlusPlus/Win64/include/TRTC
$(SolutionDir)thirdparty/IM_SDK/include
  • 라이브러리 파일 디렉터리를 추가하려면 구성 속성 > 링크 > 일반 > 추가 라이브러리 디렉터리로 이동합니다.
$(SolutionDir)thirdparty/TRTC_SDK/CPlusPlus/Win64/lib$(SolutionDir)thirdparty/IM_SDK/lib/Win64
  • 라이브러리 파일을 참조합니다.
#pragma comment(lib,"liteav.lib")
#pragma comment(lib,"ImSDK.lib")

참고:

  • 특정 비즈니스 요구에 따라 x86 통합이 필요한 경우 Win32 디렉터리의 헤더 및 라이브러리 파일을 사용하십시오.
  • DLL 동적 라이브러리는 실행 파일(exe)이 위치한 디렉터리로 복사해야 합니다.
  • x86 및 x64의 LIB 파일과 DLL 파일은 혼합할 수 없으며 일관성이 있어야 합니다.

시나리오별 구현

인증 자격 증명 생성

UserSig는 공격자가 귀하의 클라우드 계정에 접근하는 것을 방지하기 위해 Tencent Cloud에서 설계한 보안 서명입니다. 실시간 통신(TRTC) 및 채팅과 같은 클라우드 서비스는 모두 이 보안 보호 메커니즘을 채택합니다. TRTC는 방에 들어갈 때 인증을 수행하고, 채팅은 로그인 시 인증을 수행합니다.

  • 디버깅 및 테스트 단계: UserSig는 클라이언트 측 UserSig 생성TRTC 콘솔 UserSig 생성를 통해 생성할 수 있으며, 이는 디버깅 및 테스트 용도로만 사용됩니다.
  • 생산 단계: 서버 측 UserSig 생성을 사용하는 것이 권장되며, 이는 보안 수준이 높고 클라이언트가 역컴파일 및 역설계를 방지하여 키 유출 위험을 피하는 데 도움이 됩니다.

구체적인 구현 과정은 다음과 같습니다:

1. 애플리케이션이 SDK 초기화 API를 호출하기 전에 서버에서 UserSig를 요청합니다.

2. 귀하의 서버는 SDKAppID 및 UserID를 기반으로 UserSig를 생성합니다.

3. 서버는 UserSig를 귀하의 애플리케이션으로 반환합니다.

4. 귀하의 애플리케이션은 특정 API를 통해 UserSig를 SDK에 전송합니다.

5. SDK는 SDKAppID, UserID 및 UserSig를 클라우드 서버에 제출하여 검증합니다.

6. 클라우드 플랫폼은 UserSig의 유효성을 검증합니다.

7. UserSig가 유효하면 Chat SDK 및 TRTC SDK에 서비스를 제공합니다.

참고: 

  • 디버깅 및 테스트 단계 동안 로컬에서 UserSig를 생성하는 방법은 온라인 환경에서는 권장되지 않습니다. 이는 쉽게 역컴파일 및 역설계 될 수 있어 키 유출 위험이 발생할 수 있습니다. 
  • 여러 언어(Java/GO/PHP/Nodejs/Python/C#/C++)로 된 UserSig 생성 소스 코드를 제공합니다. 자세한 내용은 UserSig 생성 소스 코드.

초기화 및 리스닝

API 순서도

1.채팅 SDK 초기화 및 이벤트 리스너 추가

채팅 SDK는 함수 콜백 방식을 사용합니다. 사용을 편리하게 하기 위해 콜백 클래스로 캡슐화할 수 있습니다.

// IMWrapperCallback.h
class IMWrapperCallback
{
public:
    virtual void OnLogin(int errCode, const char* errMsg) = 0;
    virtual void OnLogout(int errCode, const char* errMsg) = 0;
    virtual void OnError(int code, const char* errMsg) = 0;
    virtual void OnCreateGroup(int errCode, const char* errMsg) = 0;
    virtual void OnJoinGroup(int errCode, const char* errMsg) = 0;
    virtual void OnRecvNewMsg(const char* msg) = 0;
    //…
};

// IMWrapper.h
class IMWrapper
{
public:
    IMWrapper();
    bool InitIM(const char* path);
    bool UnInitIM();
    void SetCallback(IMWrapperCallback* callback);

    bool LoginIM(const char* id, const char* sig);
    bool LogoutIM();

    bool CreateGroup(const char* group_name, const char* group_type, const char* group_id);
    bool JoinGroup(const char* group_id);

    bool SendGroupTextMsg(const char* group_id, const char* text);

private:
    IMWrapperCallback* m_callback;
    std::string m_userId;
};

// IMWrapper.cpp
bool IMWrapper::InitIM(const char* path)
{
    Json::Value json_value_init;
    json_value_init[kTIMSdkConfigLogFilePath] = path;
    int nRet = TIMInit(SDKAppID_IM, json_value_init.toStyledString().c_str());
    if(nRet != TIM_SUCC){
        return false;
    }

    TIMAddRecvNewMsgCallback([](const char* json_param, const void* user_data) {
        if(user_data != nullptr){
            IMWrapper* wrapper = (IMWrapper*)user_data;
            if(wrapper->m_callback != nullptr){
                wrapper->m_callback->OnRecvNewMsg(json_param);
            }
        }
    }, this);

    return true;
}

참고: 애플리케이션의 생명주기가 SDK 생명주기와 일치하는 경우, 애플리케이션 종료 전에 초기화를 해제할 필요가 없습니다. 그러나 특정 인터페이스에 들어갈 때만 SDK를 초기화하고 그 인터페이스를 종료한 후 더 이상 사용하지 않는 경우, SDK를 해제할 수 있습니다.

2.TRTC SDK에서 인스턴스 생성 및 이벤트 리스너 설정

//OnlineExam.h
#include "ITRTCCloud.h"
class OnlineExam: public ITRTCCloudCallback
{
public:
    OnlineExam();
    ~OnlineExam();
    virtual void onWarning(TXLiteAVWarning warningCode, const char* warningMsg, void* extraInfo) override;
    virtual void onError(TXLiteAVError errCode, const char *errMsg, void* extraInfo) override;
    virtual void onEnterRoom(int result) override;
    virtual void onExitRoom(int reason) override;
    //…
}

OnlineExam::OnlineExam(){
    getTRTCShareInstance()->addCallback(this);// 싱글톤 패턴을 생성하고 이벤트 리스닝을 설정합니다.
}

OnlineExam::~OnlineExam(){
    getTRTCShareInstance()->removeCallback(this);// 이벤트 리스닝을 취소합니다.
    destroyTRTCShareInstance();// 인스턴스를 파괴합니다.
}

참고: SDK 이벤트 알림을 듣는 것이 좋습니다. 일반적인 오류에 대해 로그 출력 및 처리를 수행하십시오. 자세한 내용은 오류 코드 표.

로그인 및 로그아웃

채팅 SDK를 초기화한 후, 계정 신원을 인증하고 기능을 사용할 수 있는 권한을 얻기 위해 SDK 로그인 API를 호출해야 합니다. 다른 기능을 사용하기 전에 반드시 성공적으로 로그인했는지 확인해야 하며, 그렇지 않으면 기능이 제대로 작동하지 않을 수 있습니다. TRTC의 오디오 및 비디오 서비스만 사용할 경우 이 단계를 건너뛸 수 있습니다.

API 순서도

// 로그인: userID는 사용자 정의 가능하며, userSig는 1단계에서 얻습니다.
bool IMWrapper::LoginIM(const char* id, const char* sig){
    m_userId = id;
    int nRet = TIMLogin(id, sig, [](int32_t code, const char* desc, const char* json_param, const void* user_data) {
            if(user_data != nullptr){
                IMWrapper* wrapper = (IMWrapper*)user_data;
                if(wrapper->m_callback != nullptr){
                    wrapper->m_callback->OnLogin(code, desc);
                }
            }
    }, this);

    if(nRet != TIM_SUCC){
        return false;
    }
    return true;
}

// 로그아웃
bool IMWrapper::LogoutIM(){
    int nRet = TIMLogout([](int32_t code, const char* desc, const char* json_param, const void* user_data) {
            if(user_data != nullptr){
                IMWrapper* wrapper = (IMWrapper*)user_data;
                if(wrapper->m_callback != nullptr){
                    wrapper->m_callback->OnLogout(code, desc);
                }
            }
    }, this);

    if(nRet != TIM_SUCC){
        return false;
    }
    return true;
}

참고: 애플리케이션의 생명주기가 채팅 SDK의 생명주기와 일치하는 경우, 애플리케이션 종료 전에 로그아웃할 필요가 없습니다. 그러나 특정 인터페이스에서만 채팅 SDK를 사용하고 해당 인터페이스를 종료한 후 더 이상 사용하지 않는 경우, 로그아웃하고 채팅 SDK를 초기화 해제할 수 있습니다.

방 관리

API 순서도

  1. 방 생성

앵커(방 소유자)가 생방송을 시작할 때 방을 생성해야 합니다. 여기서 "방"의 개념은 채팅에서 "그룹"에 해당합니다. 이 예시는 클라이언트에서 채팅 그룹을 생성하는 방법을 보여주지만, 서버에서 그룹을 생성하는 것도 가능합니다.

bool IMWrapper::CreateGroup(const char* name, const char* type, const char* id)
{
    Json::Value param;
    //그룹 ID
    param[kTIMCreateGroupParamGroupId] = id;
    //그룹 유형
    if (strcmp(type, "Public") == 0) {
        param[kTIMCreateGroupParamGroupType] = kTIMGroup_Public;
    }
    else if(strcmp(type, "Work") == 0) {
        param[kTIMCreateGroupParamGroupType] = kTIMGroup_Private;
    }
    else if(strcmp(type, "Meeting") == 0) {
        param[kTIMCreateGroupParamGroupType] = kTIMGroup_ChatRoom;
    }
    else if(strcmp(type, "AVChatRoom") == 0) {
        param[kTIMCreateGroupParamGroupType] = kTIMGroup_AVChatRoom;
        // 그룹에 가입하기 위한 초대 방법
        param[kTIMCreateGroupParamApproveOption] = kTIMGroupAddOpt_Forbid;//가입 불가
    }
    //그룹 이름
    param[kTIMCreateGroupParamGroupName] = name;
    std::string createParams = param.toStyledString();
    int nRet = TIMGroupCreate(createParams.c_str(), [](int32_t code, const char* desc, const char* json_params, const void* user_data)  {
            if(user_data != nullptr){
                IMWrapper* wrapper = (IMWrapper*)user_data;
                if(wrapper->m_callback != nullptr){
                    wrapper->m_callback->OnCreateGroup(code, desc);
                }
            }
    }, this);

    if(nRet != TIM_SUCC){
        return false;
    }
    return true;
}

참고:

  • 온라인 시험 시나리오를 위한 채팅 그룹 생성을 위해 Meeting Group 유형: kTIMGroup_ChatRoom을 사용하는 것이 좋습니다.
  • TRTC는 방을 생성하기 위한 별도의 단계가 없습니다. 존재하지 않는 방에 들어가면 방이 자동으로 생성됩니다.
  1. 방에 입장

채팅 그룹에 가입하기

bool IMWrapper::JoinGroup(const char* id)
{
    int nRet = TIMGroupJoin(id, "가입 희망", [](int32_t code, const char* desc, const char* json_param, const void* user_data) {
            if(user_data != nullptr){
                IMWrapper* wrapper = (IMWrapper*)user_data;
                if(wrapper->m_callback != nullptr){
                    wrapper->m_callback->OnJoinGroup(code, desc);
                }
            }
    }, this);

    if(nRet != TIM_SUCC){
        return false;
    }
    return true;
}

TRTC 방에 입장

참고:

  • TRTC 방 ID는 정수형 방 ID와 문자열형 strRoomId로 구분됩니다. 이 두 종류의 방은 서로 연결되지 않으며, 하나만 선택하면 됩니다. 방 ID 유형을 통일하는 것이 좋습니다.
  • UserSig 및 SdkAppId는 SDK를 초기화할 때 비즈니스 백엔드에서 생성하고 얻는 것이 좋으며, UserSig는 방에 입장할 때만 검증됩니다. 입장 후 만료된 UserSig는 경험에 영향을 미치지 않습니다.
  • TRTC 방 입장 시나리오는 크게 실시간 통화(AudioCall, VideoCall)와 대화형 생방송(Live, VoiceChatRoom)으로 나눌 수 있습니다. 온라인 시험 시나리오에서는 VideoCall 모드를 선택합니다.
  • TRTC 사용자 역할은 대화형 생방송 모드에서 앵커와 관객 간의 차이만 있습니다. 이 모드에서는 앵커만 스트리밍 권한이 있으며, 관객은 스트리밍을 원할 경우 앵커 역할로 전환해야 합니다. 온라인 시험 시나리오는 VideoCall 모드를 사용하므로 역할 매개변수가 필요하지 않습니다.
  • 방에 입장할 때 이벤트 콜백에서 result > 0은 방에 들어가는 데 걸린 시간(밀리초)을 나타내고, result < 0은 방에 들어가는 데 실패한 오류 코드를 나타냅니다. 자세한 내용은 오류 코드 표.
// 방에 입장.
void OnlineExam::EnterExamRoom(String roomId, String userId, String userName, int roleType, String userSig)
{
    TRTCParams param;
    param.sdkAppId = SDKAppID_TRTC;
    param.strRoomId = roomID.c_str();
    param.userId = userID.c_str();
    param.userSig = userSig.c_str();
    getTRTCShareInstance()->enterRoom(param, TRTCAppSceneVideoCall);
}

// 방 입장의 결과에 대한 이벤트 콜백.
void OnlineExam::onEnterRoom(int result)
{    
    if (result > 0) {
        // 방에 성공적으로 입장했습니다.
    } else {
        // 방에 입장하는 데 실패했습니다.
    }
}
  1. 방에서 나가기
// 방에서 나가기.
void OnlineExam::ExitExamRoom()
{
   getTRTCShareInstance()->exitRoom();
}

// 방 퇴장 이벤트 콜백.
void OnlineExam::onExitRoom(int reason)
{
    // 0: exitRoom을 호출하여 능동적으로 방에서 나간 경우; 1: 서버에 의해 현재 방에서 제거된 경우; 2: 현재 방이 해산된 경우.
}

오디오 스트림 관리

TRTC SDK는 기본적으로 오디오 스트림을 자동으로 구독하도록 설정되어 있으며, 사용자가 방에 들어가면 원격 사용자의 오디오가 자동으로 재생됩니다. 오디오 스트림을 수동으로 구독해야 하는 경우, 구독 모드 설정.

  1. 학생 측 스트리밍
// 방에 성공적으로 입장한 후, 학생이 오디오 및 비디오 스트리밍을 시작합니다.
void OnlineExam::onEnterRoom(int result){
    if (result > 0) {
        // 방에 성공적으로 입장했습니다.
        getTRTCShareInstance()->startLocalAudio(TRTCAudioQualitySpeech);
        getTRTCShareInstance()->startLocalPreview(hwndView);
    } else {
        // 방에 입장하는 데 실패했습니다.
    }
}

// 시험이 종료되면 스트리밍을 중지합니다.
void OnlineExam::ExitExamRoom(){
    getTRTCShareInstance()->stopLocalAudio();
    getTRTCShareInstance()->stopLocalPreview();
    getTRTCShareInstance()->exitRoom();
}
  1. 모니터링 측 스트리밍 풀링

학생들이 방에 먼저 입장하고 스트리밍을 시작했으므로, 감독관은 방에 성공적으로 입장한 후 즉시 스트림을 풀링할 수 있습니다. 원격 스트림 이벤트 콜백을 수신한 후, 현재 페이지의 학생인지 확인할 수 있습니다. 그렇다면 다시 호출하여 스트림 풀링을 시작할 수 있으며 아무런 영향이 없습니다.

void OnlineExam::onUserVideoAvailable(const char* userId, bool available){
    if(available){
        if(userId == 현재 페이지의 학생){
            getTRTCShareInstance()->startRemoteView(userId, TRTCVideoStreamTypeBig, hwndView);
        }
    }
}

고급 기능

시험실 분할 관리

온라인 시험 시나리오에서는 수천 또는 수만 명의 학생이 한 번에 시험을 치를 수 있습니다. 그러나 TRTC 방은 방당 50명의 동시 스트리머로 제한되어 있습니다. 따라서 학생들은 학생 수에 따라 가상 시험실로 나누어져야 합니다. 시험이 그렇게 엄격하지 않은 경우, 학생들은 단일 비디오 스트림만 사용할 수 있으며, 하나의 스트림은 감독관을 위해 예약됩니다. 각 가상 시험실에는 40-49명의 학생을 같은 roomId로 지정하는 것이 좋습니다. 방에 입장한 후 학생들은 스트리밍만 하고 스트리밍을 풀링하지 않아야 합니다.

같은 시험을 동시에 치르는 학생들은 비즈니스 백엔드에서 50명 이하의 학생이 있는 가상 시험실로 나누어야 합니다. 각 가상 시험실은 TRTC 방 ID(roomId)에 매핑되어야 합니다. 시험 전에 학생들에게 30분 전에 시험실에 들어가 대기하도록 안내해야 합니다. 방에 들어간 후 학생들은 카메라, 마이크 및 스피커 테스트를 완료하도록 안내해야 합니다. 스트리밍은 5분 전에 시작할 수 있으며, 학생 스트리밍의 문제를 조기에 발견할 수 있도록 해야 합니다.

// 학생 측 (웹을 예로 듦).

/// 카메라 테스트.
TRTC.startLocalVideo({ view: 'camera-video', publish: false });
/// 마이크 테스트.
TRTC.startLocalAudio({ publish: false });
// 스피커 테스트를 위해 재생 가능한 mp3 URL을 준비해 주세요. < /audio>
const audioPlayer = document.getElementById('audio-player');
if (!audioPlayer.paused) {
    audioPlayer.pause();
}
audioPlayer.currentTime = 0;

/// 스트리밍만 푸시하고 스트리밍을 풀링하지 않습니다.
const localStream = TRTC.createStream({ userId, audio: true, video: true });
try {
  await localStream.initialize();
  console.log('로컬 스트림 초기화 성공');
} catch (error) {
  console.error('로컬 스트림 초기화 실패: ' + error);
}
try {
  await client.publish(localStream);
  console.log('로컬 스트림 게시 성공');
} catch (error) {
  console.error('로컬 스트림 게시 실패: ' + error);
}

TRTC는 장치 감지 React 컴포넌트를 제공하며, 이를 통해 장치 감지 기능을 빠르게 통합하는 것이 좋습니다.

비디오 페이징 관리

감독관 측에서는 모든 학생의 비디오를 한 번에 풀링하는 것을 권장하지 않습니다. 이는 매우 높은 대역폭을 요구하기 때문입니다. 예를 들어, 360P 비디오 스트림은 400-600 kbps의 대역폭을 요구하므로 40명의 학생이 있을 경우 16-24 Mbps의 대역폭이 필요합니다. 만약 10명의 감독관이 같은 사무실에서 스트림을 풀링한다면, 160-240 Mbps의 대역폭이 필요합니다. 따라서 시험 모니터링 단말기는 페이지네이션 디스플레이를 구현할 수 있습니다. 예를 들어 한 페이지에 9개의 스트림을 표시하고, 다음 페이지를 클릭하면 현재 9개의 스트림을 중지하고 다음 9개의 스트림을 풀링합니다.

// 감독관 측 (윈도우를 예로 듦).
void OnlineExam::onClickNext(){
    getTRTCShareInstance()->stopAllRemoteView();
    getTRTCShareInstance()->muteAllRemoteAudio(true);
    
    for(int i = 0; i < m_curUserList.size(); i++){
       getTRTCShareInstance()->startRemoteView(m_curUserList[i].userId, TRTCVideoStreamTypeBig, m_hwndList[i]);
    }
}

참고: 기본 스트리밍은 비동기 작업이므로 약간의 지연이 발생할 수 있으므로 페이지네이션 버튼의 클릭 빈도를 제한해야 하며, 1-2초 정도로 제한하는 것이 좋습니다. 너무 자주 클릭하면 기본 시스템의 응답이 지연되어 예기치 않은 예외가 발생할 수 있습니다.

분할 화면 표시

일반적인 온라인 시험에서는 웹캠이 노트북 내장 웹캠을 사용하는데, 이는 학생의 앞면만 촬영할 수 있습니다. 더 엄격한 시험의 경우 이는 요구 사항을 충족하지 않습니다. 시험 중 부정행위를 방지하기 위해 학생의 뒷면 방향도 촬영해야 합니다.

단일 스트림으로 모니터링하는 것과는 달리, 추가적인 측면 배경 비디오 모니터링이 추가되며, 이는 일반적으로 모바일 폰에서 수행되어 이동 및 위치 조정이 용이합니다. 학생들에게 세팅 방법에 대한 안내가 필요하며, 세팅 후 정면 및 측면 비디오의 프레임 이미지를 캡처하여 비즈니스 백엔드로 보내 평가하여 학생의 포지셔닝이 적합한지 자동으로 판단합니다.

예를 들어, 사용자 ID가 1234이면 측면 비디오 사용자 ID에 1234_side와 같은 사용자 정의 접미사를 추가할 수 있습니다.

// 학생 측 - 정면 비디오 (웹을 예로 듦).
const trtc = TRTC.create();
await trtc.enterRoom({ roomId, sdkAppId, "1234", userSig });

const localStream = TRTC.createStream({ userId, audio: true, video: true });
try {
  await localStream.initialize();
  console.log('로컬 스트림 초기화 성공');
} catch (error) {
  console.error('로컬 스트림 초기화 실패: ' + error);
}
try {
  await client.publish(localStream);
  console.log('로컬 스트림 게시 성공');
} catch (error) {
  console.error('로컬 스트림 게시 실패: ' + error);
}
// 학생 측 - 측면 비디오 (안드로이드를 예로 듦).
TRTCCloudDef.TRTCParams params = new TRTCCloudDef.TRTCParams();
params.sdkAppId = SDKAPPID;
params.userId = "1234_side";
params.roomId = roomId;
params.userSig = usersig;

mCloud.enterRoom(params, TRTCCloudDef.TRTC_APP_SCENE_VIDEOCALL);  

mTRTCCloud.startLocalPreview(true, mTXCloudPreviewView);
mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);

음량 감지

더 엄격한 온라인 시험 시나리오에서는 수험자의 정면 및 측면 뷰를 모니터링하는 것뿐만 아니라, 수험자의 주변 소리를 감지하는 것도 필수적입니다. 이는 수험자가 사운드를 통해 부정행위를 시도할 수 있기 때문입니다. 예를 들어, 녹음된 자료를 재생하거나 다른 사람과 대화하거나 전화 통화를 하는 등의 행동이 있을 수 있습니다.

TRTC SDK는 음량 감지 콜백 기능을 제공합니다. 이 기능은 각 사용자의 음량 수준을 실시간으로 모니터링하여 의심스러운 부정행위 행동을 감지하는 데 도움을 줍니다. 예를 들어, 수험자의 목소리가 갑자기 커지면, 그는 미리 녹음된 답변을 재생하거나 다른 사람과 대화하고 있을 수 있습니다. 이 경우 교사는 해당 수험자의 오디오를 들음으로써 개입할 수 있습니다.

API 순서도

// 자동 스트림 풀링 비활성화.
void OnlineExam::enterExamRoom(){
    getTRTCShareInstance()->setDefaultStreamRecvMode(false, false);
    getTRTCShareInstance()->enterRoom(params);    
    getTRTCShareInstance()->enableAudioVolumeEvaluation();
}

// 현재 페이지네이션 사용자들의 스트림을 풀링합니다.
void OnlineExam::onEnterRoom(){
    for(int i = 0; i < m_curUserList.size(); i++){
       getTRTCShareInstance()->muteRemoteAudio(m_curUserList[i].userId, false);
       getTRTCShareInstance()->startRemoteView(m_curUserList[i].userId, TRTCVideoStreamTypeBig, m_hwndList[i]);
    }
}

// 음량 수준을 감지합니다.
void OnlineExam::onUserVoiceVolume(TRTCVolumeInfo* userVolumes, uint32_t userVolumesCount, uint32_t totalVolume){
    int count = userVolumesCount;
    TRTCVolumeInfo* remoteUserVolumes = userVolumes;
    std::string userId;
    int userVolume = 0;
    for(int i = 0; i < count; i++){
        userId = remoteUserVolumes->userId;
        if(!userId.empty()){
            userVolume = remoteUserVolumes->volume;
            if(userVolume > m_maxVolume){
                // 인터페이스에서 해당 학생을 표시합니다.
            }
        }
        remoteUserVolumes++;
    }
}

지금 주문하기

구매 페이지로 빠르게 이동하려면 여기를 클릭하세요 주문하세요.