통화 중 비디오 관리

Azure Communication Services SDKS를 사용하여 화상 통화를 관리하는 방법을 알아봅니다. 통화 내에서 비디오 수신 및 보내기를 관리하는 방법을 알아봅니다.

필수 조건

SDK 설치

명령을 npm install 사용하여 JavaScript용 Azure Communication Services Common 및 Calling SDK를 설치합니다.

npm install @azure/communication-common --save
npm install @azure/communication-calling --save

필수 개체 초기화

CallClient 인스턴스는 대부분의 호출 작업에 필요합니다. 여기서는 새 CallClient 인스턴스를 만듭니다. 인스턴스와 같은 Logger 사용자 지정 옵션을 사용하여 구성할 수 있습니다.

CallClient 인스턴스가 있으면 CallClient 인스턴스에서 createCallAgent 메서드를 호출하여 CallAgent 인스턴스를 만들 수 있습니다. 이 메서드는 CallAgent 인스턴스 개체를 비동기적으로 반환됩니다.

createCallAgent 메서드는 CommunicationTokenCredential을 인수로 사용합니다. 사용자 액세스 토큰이 허용됩니다.

CallClient 인스턴스에서 getDeviceManager 메서드를 사용하여 deviceManager에 액세스할 수 있습니다.

const { CallClient } = require('@azure/communication-calling');
const { AzureCommunicationTokenCredential} = require('@azure/communication-common');
const { AzureLogger, setLogLevel } = require("@azure/logger");

// Set the logger's log level
setLogLevel('verbose');

// Redirect log output to console, file, buffer, REST API, or whatever location you want
AzureLogger.log = (...args) => {
    console.log(...args); // Redirect log output to console
};

const userToken = '<USER_TOKEN>';
callClient = new CallClient(options);
const tokenCredential = new AzureCommunicationTokenCredential(userToken);
const callAgent = await callClient.createCallAgent(tokenCredential, {displayName: 'optional Azure Communication Services user name'});
const deviceManager = await callClient.getDeviceManager()

디바이스 관리

통화로 비디오 사용을 시작하려면 디바이스를 관리하는 방법을 알아야 합니다. 디바이스를 사용하면 오디오 및 비디오를 통화로 전송하는 것을 제어할 수 있습니다.

에서 deviceManager통화에서 오디오 및 비디오 스트림을 전송할 수 있는 로컬 디바이스를 열거할 수 있습니다. 또한 이를 사용하여 로컬 디바이스의 마이크 및 카메라에 대한 액세스 권한을 요청할 수 있습니다.

callClient.getDeviceManager() 메서드를 호출하여 deviceManager에 액세스할 수 있습니다.

const deviceManager = await callClient.getDeviceManager();

로컬 디바이스 가져오기

로컬 디바이스에 액세스하려면 .에서 열거형 메서드를 deviceManager사용할 수 있습니다. 열거형은 비동기 작업입니다.

//  Get a list of available video devices for use.
const localCameras = await deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]

// Get a list of available microphone devices for use.
const localMicrophones = await deviceManager.getMicrophones(); // [AudioDeviceInfo, AudioDeviceInfo...]

// Get a list of available speaker devices for use.
const localSpeakers = await deviceManager.getSpeakers(); // [AudioDeviceInfo, AudioDeviceInfo...]

기본 마이크 및 스피커 설정

에서 deviceManager통화를 시작하는 데 사용하는 기본 디바이스를 설정할 수 있습니다. 클라이언트 기본값이 설정되지 않은 경우 Communication Services는 운영 체제 기본값을 사용합니다.

// Get the microphone device that is being used.
const defaultMicrophone = deviceManager.selectedMicrophone;

// Set the microphone device to use.
await deviceManager.selectMicrophone(localMicrophones[0]);

// Get the speaker device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;

// Set the speaker device to use.
await deviceManager.selectSpeaker(localSpeakers[0]);

각각 CallAgent 연결된 DeviceManager마이크와 스피커를 선택할 수 있습니다. 다른 CallAgents 마이크와 스피커를 사용하는 것이 좋습니다. 동일한 마이크나 스피커를 공유해서는 안 됩니다. 공유의 경우 마이크 UFD가 트리거될 수 있으며 마이크는 브라우저/os에 따라 작동을 중지합니다.

로컬 비디오 스트림 속성

A LocalVideoStream 에는 다음과 같은 속성이 있습니다.

  • source: 디바이스 정보입니다.
const source = localVideoStream.source;
  • mediaStreamType: 수 Video, ScreenSharing또는 RawMedia.
const type: MediaStreamType = localVideoStream.mediaStreamType;

로컬 카메라 미리 보기

로컬 카메라에서 스트림 렌더링을 시작하고 사용할 deviceManagerVideoStreamRenderer 수 있습니다. 이 스트림은 다른 참가자에게 전송되지 않습니다. 로컬 미리 보기 피드입니다.

// To start viewing local camera preview
const cameras = await deviceManager.getCameras();
const camera = cameras[0];
const localVideoStream = new LocalVideoStream(camera);
const videoStreamRenderer = new VideoStreamRenderer(localVideoStream);
const view = await videoStreamRenderer.createView();
htmlElement.appendChild(view.target);

// To stop viewing local camera preview
view.dispose();
htmlElement.removeChild(view.target);

카메라 및 마이크에 대한 권한 요청

사용자에게 카메라 및/또는 마이크 사용 권한을 부여하라는 메시지를 표시합니다.

const result = await deviceManager.askDevicePermission({audio: true, video: true});

출력은 권한이 부여되었는지 여부 audio 와 권한을 나타내는 개체와 video 함께 반환됩니다.

console.log(result.audio);
console.log(result.video);

주의

  • videoDevicesUpdated 비디오 디바이스가 플러그 인/분리되면 이벤트가 발생합니다.
  • audioDevicesUpdated 오디오 디바이스가 연결되면 이벤트가 발생합니다.
  • DeviceManager를 만들 때 사용 권한이 아직 부여되지 않은 경우 처음에는 디바이스에 대해 알지 못하므로 처음에는 디바이스 목록이 비어 있습니다. 그런 다음 DeviceManager.askPermission() API를 호출하면 사용자에게 디바이스 액세스에 대한 메시지가 표시됩니다. 사용자가 '허용'을 클릭하여 디바이스 관리자가 시스템의 디바이스에 대해 학습하는 액세스 권한을 부여하면 디바이스 목록을 업데이트하고 'audioDevicesUpdated' 및 'videoDevicesUpdated' 이벤트를 내보냅니다. 사용자가 페이지를 새로 고치고 디바이스 관리자를 만드는 경우 사용자가 이전에 액세스 권한을 이미 부여했으므로 디바이스 관리자가 디바이스에 대해 알아볼 수 있습니다. 처음에는 디바이스 목록이 채워지고 'audioDevicesUpdated' 또는 'videoDevicesUpdated' 이벤트를 내보내지 않습니다.
  • 스피커 열거형/선택은 Android Chrome, iOS Safari 또는 macOS Safari에서 지원되지 않습니다.

비디오 카메라로 전화 걸기

Important

현재 발신 로컬 비디오 스트림은 하나만 지원됩니다.

화상 통화를 하려면 deviceManager에서 getCameras() 메서드를 사용하여 로컬 카메라를 열거해야 합니다.

카메라를 선택한 후에는 이를 사용하여 LocalVideoStream 인스턴스를 생성합니다. 이를 videoOptions 내에서 localVideoStream 배열 내의 한 항목으로 startCall 메서드에 전달합니다.

const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
const placeCallOptions = {videoOptions: {localVideoStreams:[localVideoStream]}};
const userCallee = { communicationUserId: '<ACS_USER_ID>' }
const call = callAgent.startCall([userCallee], placeCallOptions);
  • CallAgent.join() API를 사용하여 비디오로 통화에 참여하고 Call.Accept() API를 사용하여 비디오로 수락하고 통화할 수도 있습니다.
  • 전화가 연결되면 선택한 카메라에서 다른 참가자로 비디오 스트림을 자동으로 보내기 시작합니다.

통화 중 로컬 비디오 보내기 시작 및 중지

통화 중에 비디오를 시작하려면 deviceManager 개체의 getCameras 메서드를 사용하여 카메라를 열거해야 합니다. 그런 다음, 원하는 카메라로 LocalVideoStream의 새 인스턴스를 만든 다음, LocalVideoStream 개체를 기존 호출 개체의 startVideo 메서드에 전달합니다.

const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
await call.startVideo(localVideoStream);

비디오 보내기를 LocalVideoStream 성공적으로 시작하면 호출 인스턴스의 컬렉션에 형식 Video 인스턴스가 추가 localVideoStreams 됩니다.

const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'Video'} );

통화하는 동안 로컬 비디오를 중지하려면 비디오에 사용되는 인스턴스를 전달 localVideoStream 합니다.

await call.stopVideo(localVideoStream);

인스턴스에서 호출 switchSource 하여 비디오가 전송되는 동안 다른 카메라 디바이스로 localVideoStream 전환할 수 있습니다.

const cameras = await callClient.getDeviceManager().getCameras();
const camera = cameras[1];
localVideoStream.switchSource(camera);

지정된 비디오 디바이스가 다른 프로세스에서 사용 중이거나 시스템에서 사용하도록 설정되지 않은 경우:

  • 통화하는 동안 비디오가 꺼져 있고 비디오를 사용하여 call.startVideo()시작하는 경우 이 메서드는 a SourceUnavailableError 를 throw하고 cameraStartFiled 사용자 연결 진단은 true로 설정됩니다.
  • 메서드 cameraStartFailedlocalVideoStream.switchSource() 호출하면 true로 설정됩니다. 통화 진단 가이드에서는 통화 관련 문제를 진단하는 방법에 대한 추가 정보를 제공합니다.

로컬 비디오가 켜지거나 꺼져 있는지 확인하려면 true 또는 false를 반환하는 isLocalVideoStarted API를 사용할 수 있습니다.

// Check if local video is on or off
call.isLocalVideoStarted;

로컬 비디오의 변경 내용을 수신 대기하려면 isLocalVideoStartedChanged 이벤트를 구독하고 구독을 취소할 수 있습니다.

// Subscribe to local video event
call.on('isLocalVideoStartedChanged', () => {
    // Callback();
});
// Unsubscribe from local video event
call.off('isLocalVideoStartedChanged', () => {
    // Callback();
});

통화 중 화면 공유 시작 및 중지

통화하는 동안 화면 공유를 시작하려면 비동기 API startScreenSharing을 사용할 수 있습니다.

// Start screen sharing
await call.startScreenSharing();

화면 공유 LocalVideoStream 를 성공적으로 보내면 호출 인스턴스의 컬렉션에 형식 ScreenSharing의 인스턴스가 추가 localVideoStreams 됩니다.

const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing'} );

통화 중 화면 공유를 중지하려면 비동기 API stoptScreenSharing을 사용할 수 있습니다.

// Stop screen sharing
await call.stopScreenSharing();

화면 공유가 설정 또는 해제되어 있는지 확인하려면 true 또는 false를 반환하는 isScreenSharingOn API를 사용할 수 있습니다.

// Check if screen sharing is on or off
call.isScreenSharingOn;

화면 공유에 대한 변경 내용을 수신 대기하려면 isScreenSharingOnChanged 이벤트를 구독하고 구독을 취소할 수 있습니다.

// Subscribe to screen share event
call.on('isScreenSharingOnChanged', () => {
    // Callback();
});
// Unsubscribe from screen share event
call.off('isScreenSharingOnChanged', () => {
    // Callback();
});

Important

Azure Communication Services의 이 기능은 현재 미리 보기 상태입니다.

미리 보기 API 및 SDK는 서비스 수준 계약 없이 제공됩니다. 프로덕션 워크로드에는 사용하지 않는 것이 좋습니다. 일부 기능은 지원되지 않거나 기능이 제한될 수 있습니다.

자세한 내용은 Microsoft Azure 미리 보기에 대한 보충 사용 약관을 검토하세요.

로컬 화면 공유 미리 보기는 공개 미리 보기로 제공되며 버전 1.15.1-beta.1 이상의 일부로 사용할 수 있습니다.

로컬 화면 공유 미리 보기

VideoStreamRenderer 로컬 화면 공유에서 스트림 렌더링을 시작하여 화면 공유 스트림으로 보내는 내용을 볼 수 있습니다.

// To start viewing local screen share preview
await call.startScreenSharing();
const localScreenSharingStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing' });
const videoStreamRenderer = new VideoStreamRenderer(localScreenSharingStream);
const view = await videoStreamRenderer.createView();
htmlElement.appendChild(view.target);

// To stop viewing local screen share preview.
await call.stopScreenSharing();
view.dispose();
htmlElement.removeChild(view.target);

// Screen sharing can also be stoped by clicking on the native browser's "Stop sharing" button.
// The isScreenSharingOnChanged event will be triggered where you can check the value of call.isScreenSharingOn.
// If the value is false, then that means screen sharing is turned off and so we can go ahead and dispose the screen share preview.
// This event is also triggered for the case when stopping screen sharing via Call.stopScreenSharing() API.
call.on('isScreenSharingOnChanged', () => {
    if (!call.isScreenSharingOn) {
        view.dispose();
        htmlElement.removeChild(view.target);
    }
});

원격 참가자 비디오/화면 공유 스트림 렌더링

원격 참가자의 비디오 스트림과 화면 공유 스트림을 나열하려면 videoStreams 컬렉션을 검사합니다.

const remoteVideoStream: RemoteVideoStream = call.remoteParticipants[0].videoStreams[0];
const streamType: MediaStreamType = remoteVideoStream.mediaStreamType;

렌더링 RemoteVideoStream하려면 해당 isAvailableChanged 이벤트를 구독해야 합니다. 속성이 isAvailable 변경 true되면 원격 참가자가 스트림을 보냅니다. 이 경우 새 인스턴스를 VideoStreamRenderer만든 다음 비동 createView 기 메서드를 사용하여 새 VideoStreamRendererView 인스턴스를 만듭니다. 그런 다음 모든 UI 요소에 연결할 view.target 수 있습니다.

원격 스트림의 가용성이 변경되면 전체 VideoStreamRenderer 또는 특정 VideoStreamRendererView스트림을 삭제할 수 있습니다. 유지하려는 경우 보기에 빈 비디오 프레임이 표시됩니다.

// Reference to the html's div where we would display a grid of all remote video stream from all participants.
let remoteVideosGallery = document.getElementById('remoteVideosGallery');

subscribeToRemoteVideoStream = async (remoteVideoStream) => {
   let renderer = new VideoStreamRenderer(remoteVideoStream);
    let view;
    let remoteVideoContainer = document.createElement('div');
    remoteVideoContainer.className = 'remote-video-container';

    let loadingSpinner = document.createElement('div');
    // See the css example below for styling the loading spinner.
    loadingSpinner.className = 'loading-spinner';
    remoteVideoStream.on('isReceivingChanged', () => {
        try {
            if (remoteVideoStream.isAvailable) {
                const isReceiving = remoteVideoStream.isReceiving;
                const isLoadingSpinnerActive = remoteVideoContainer.contains(loadingSpinner);
                if (!isReceiving && !isLoadingSpinnerActive) {
                    remoteVideoContainer.appendChild(loadingSpinner);
                } else if (isReceiving && isLoadingSpinnerActive) {
                    remoteVideoContainer.removeChild(loadingSpinner);
                }
            }
        } catch (e) {
            console.error(e);
        }
    });

    const createView = async () => {
        // Create a renderer view for the remote video stream.
        view = await renderer.createView();
        // Attach the renderer view to the UI.
        remoteVideoContainer.appendChild(view.target);
        remoteVideosGallery.appendChild(remoteVideoContainer);
    }

    // Remote participant has switched video on/off
    remoteVideoStream.on('isAvailableChanged', async () => {
        try {
            if (remoteVideoStream.isAvailable) {
                await createView();
            } else {
                view.dispose();
                remoteVideosGallery.removeChild(remoteVideoContainer);
            }
        } catch (e) {
            console.error(e);
        }
    });

    // Remote participant has video on initially.
    if (remoteVideoStream.isAvailable) {
        try {
            await createView();
        } catch (e) {
            console.error(e);
        }
    }
    
    console.log(`Initial stream size: height: ${remoteVideoStream.size.height}, width: ${remoteVideoStream.size.width}`);
    remoteVideoStream.on('sizeChanged', () => {
        console.log(`Remote video stream size changed: new height: ${remoteVideoStream.size.height}, new width: ${remoteVideoStream.size.width}`);
    });
}

원격 비디오 스트림을 통해 로딩 스피너를 스타일링하기 위한 CSS입니다.

.remote-video-container {
   position: relative;
}
.loading-spinner {
   border: 12px solid #f3f3f3;
   border-radius: 50%;
   border-top: 12px solid #ca5010;
   width: 100px;
   height: 100px;
   -webkit-animation: spin 2s linear infinite; /* Safari */
   animation: spin 2s linear infinite;
   position: absolute;
   margin: auto;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
   transform: translate(-50%, -50%);
}
@keyframes spin {
   0% { transform: rotate(0deg); }
   100% { transform: rotate(360deg); }
}
/* Safari */
@-webkit-keyframes spin {
   0% { -webkit-transform: rotate(0deg); }
   100% { -webkit-transform: rotate(360deg); }
}

원격 비디오 품질

Azure Communication Services WebJS SDK는 버전 1.15.1부터 OVC(최적 비디오 수)라는 기능을 제공합니다. 이 기능을 사용하여 런타임에 애플리케이션에 그룹 통화에서 특정 순간에 최적으로 렌더링할 수 있는 다른 참가자의 수신 비디오 수(2+ 참가자)를 알릴 수 있습니다. 이 기능은 로컬 엔드포인트의 네트워크 및 하드웨어 기능을 기반으로 호출 중에 동적으로 변경되는 속성을 optimalVideoCount 노출합니다. 지정된 순간에 다른 참가자 애플리케이션에서 렌더링해야 하는 비디오 수에 대한 세부 정보 값 optimalVideoCount 입니다. 애플리케이션은 권장 사항에 따라 이러한 변경 내용을 처리하고 렌더링된 비디오 수를 업데이트해야 합니다. 업데이트 사이에 너무 자주 변경되지 않도록 하는 쿨다운 기간(약 10개)이 있습니다.

사용optimalVideoCount 기능은 호출 기능입니다.

interface OptimalVideoCountCallFeature extends CallFeature {
    off(event: 'optimalVideoCountChanged', listener: PropertyChangedEvent): void;
    on(event: 'optimalVideoCountChanged', listener: PropertyChangedEvent): void;
    readonly optimalVideoCount: number;
}

const optimalVideoCountFeature = call.feature(Features.OptimalVideoCount);
optimalVideoCountFeature.on('optimalVideoCountChanged', () => {
    const localOptimalVideoCountVariable = optimalVideoCountFeature.optimalVideoCount;
})

사용 예: 애플리케이션은 OVC의 변경 내용을 구독하고 새 렌더러(createView메서드)를 만들거나 보기()를 삭제하고 그에 따라 참가자를dispose 기본 호출 화면 영역(스테이지 또는 명단이라고도 함)에서 제거하거나 비디오 요소를 아바타와 사용자의 이름으로 바꿔서 레이아웃을 업데이트하여 그룹 호출에서 처리해야 합니다.

원격 비디오 스트림 속성

원격 비디오 스트림에는 다음과 같은 속성이 있습니다.

  • id: 원격 비디오 스트림의 ID입니다.
const id: number = remoteVideoStream.id;
  • mediaStreamType: Video 또는 ScreenSharing일 수 있습니다.
const type: MediaStreamType = remoteVideoStream.mediaStreamType;
  • isAvailable: 원격 참가자 엔드포인트가 스트림을 적극적으로 보내는지 여부를 정의합니다.
const isAvailable: boolean = remoteVideoStream.isAvailable;
  • isReceiving:
    • 원격 비디오 스트림 데이터가 수신되고 있는지 여부를 애플리케이션에 알릴 수 있습니다. 이러한 시나리오는 다음과 같습니다.
      • 모바일 브라우저에 있는 원격 참가자의 비디오를 보고 있습니다. 원격 참가자는 모바일 브라우저 앱을 백그라운드로 가져옵니다. 이제 RemoteVideoStream.isReceiving 플래그가 false로 이동하고 검은 색 프레임 / 얼어 붙은 비디오를 볼 수 있습니다. 원격 참가자가 모바일 브라우저를 다시 포그라운드로 가져오면 이제 RemoteVideoStream.isReceiving 플래그가 true로 다시 표시되고 비디오가 정상적으로 재생되는 것을 볼 수 있습니다.
      • 어떤 플랫폼에 있든 원격 참가자의 비디오를 보고 있습니다. 양쪽에서 네트워크 문제가 있고, 비디오의 품질이 나빠지기 시작합니다. 아마도 네트워크 문제로 인해 RemoteVideoStream.isReceiving 플래그가 false로 가는 것을 볼 수 있습니다.
      • macOS/iOS Safari에 있는 원격 참가자의 비디오를 보고 있으며 주소 표시줄에서 "일시 중지" /"다시 시작" 카메라를 클릭합니다. 카메라를 일시 중지한 후 검은색/고정된 비디오가 표시되고 RemoteVideoStream.isReceiving 플래그가 false로 이동되는 것을 볼 수 있습니다. 카메라 재생을 다시 시작하면 RemoteVideoStream.isReceiving 플래그가 true로 전환되는 것을 볼 수 있습니다.
      • 모든 플랫폼에 있고 네트워크 연결이 끊긴 원격 참가자의 비디오를 보고 있습니다. 원격 참가자는 약 2 분 동안 통화에 머물 것이며 비디오가 얼어 붙은 / 검은 색 프레임을 볼 수 있습니다. RemoteVideoStream.isReceiving 플래그는 false로 이동합니다. 원격 참가자는 네트워크를 다시 가져와 다시 연결할 수 있으며 오디오/비디오가 정상적으로 흐르기 시작해야 하며 RemoteVideoStream.isReceiving 플래그가 true로 표시됩니다.
      • 모바일 브라우저에 있는 원격 참가자의 비디오를 보고 있습니다. 원격 참가자가 모바일 브라우저를 종료합니다. 원격 참가자가 모바일에 있었기 때문에 실제로 약 2 분 동안 통화에 참가자를 남겨 둡니다. 나는 아직도 통화에서 그들을 볼 수 있으며 그들의 비디오는 얼어 붙을 것입니다. RemoteVideoStream.isReceiving 플래그는 false로 이동합니다. 어떤 시점에서 서비스는 통화에서 참가자를 내보내고 참가자가 통화에서 연결이 끊어진 것을 볼 수 있습니다.
      • 모바일 브라우저에서 장치를 잠그는 원격 참가자의 비디오를 보고 있습니다. RemoteVideoStream.isReceiving 플래그가 false로 이동합니다. 원격 참가자가 디바이스의 잠금을 해제하고 Azure Communication Services 호출로 이동하면 플래그가 true로 돌아가는 것을 볼 수 있습니다. 원격 참가자가 데스크톱에 있고 데스크톱 잠금/절전 모드인 경우 동일한 동작
    • 이 기능은 원격 비디오 스트림을 렌더링하기 위한 사용자 환경을 개선합니다.
    • isReceiving 플래그가 false로 변경되면 원격 비디오 스트림에 로딩 스피너를 표시할 수 있습니다. 로딩 스피너를 수행할 필요가 없으며 원하는 모든 작업을 수행할 수 있지만 로딩 스피너는 더 나은 사용자 환경을 위한 가장 일반적인 사용법입니다.
const isReceiving: boolean = remoteVideoStream.isReceiving;
  • size: 스트림 크기입니다. 스트림 크기가 클수록 비디오 품질이 향상됩니다.
const size: StreamSize = remoteVideoStream.size;

VideoStreamRenderer 메서드 및 속성

VideoStreamRendererView 애플리케이션 UI에 연결하여 원격 비디오 스트림을 렌더링하고, 비동 createView() 기 메서드를 사용하고, 스트림을 렌더링할 준비가 되면 확인하고, DOM 트리의 아무 곳에나 추가할 수 있는 요소를 나타내는 video 속성을 가진 개체 target 를 반환하는 인스턴스를 만듭니다.

await videoStreamRenderer.createView();

연결된 VideoStreamRendererView 모든 인스턴스를 videoStreamRenderer 삭제합니다.

videoStreamRenderer.dispose();

VideoStreamRendererView 메서드 및 속성

VideoStreamRendererView를 만들 때 scalingModeisMirrored 속성을 지정할 수 있습니다. scalingModeStretch, Crop 또는 Fit일 수 있습니다. 지정된 경우 isMirrored 렌더링된 스트림은 세로로 대칭 이동됩니다.

const videoStreamRendererView: VideoStreamRendererView = await videoStreamRenderer.createView({ scalingMode, isMirrored });

모든 VideoStreamRendererView 인스턴스에는 target 렌더링 화면을 나타내는 속성이 있습니다. 애플리케이션 UI에서 이 속성을 연결합니다.

htmlElement.appendChild(view.target);

메서드를 호출하여 업데이트 scalingMode 할 수 있습니다.updateScalingMode

view.updateScalingMode('Crop');

동일한 데스크톱 디바이스에서 동일한 호출로 두 개의 서로 다른 카메라에서 비디오 스트림을 보냅니다.

Important

Azure Communication Services의 이 기능은 현재 미리 보기 상태입니다.

미리 보기 API 및 SDK는 서비스 수준 계약 없이 제공됩니다. 프로덕션 워크로드에는 사용하지 않는 것이 좋습니다. 일부 기능은 지원되지 않거나 기능이 제한될 수 있습니다.

자세한 내용은 Microsoft Azure 미리 보기에 대한 보충 사용 약관을 검토하세요.

이는 데스크톱 지원 브라우저에서 버전 1.17.1-beta.1+의 일부로 지원됩니다.

  • 다음 코드 조각을 사용하여 동일한 호출로 단일 데스크톱 브라우저 탭/앱에서 두 개의 서로 다른 카메라에서 비디오 스트림을 보낼 수 있습니다.
// Create your first CallAgent with identity A
const callClient1 = new CallClient();
const callAgent1 = await callClient1.createCallAgent(tokenCredentialA);
const deviceManager1 = await callClient1.getDeviceManager();

// Create your second CallAgent with identity B
const callClient2 = new CallClient();
const callAgent2 = await callClient2.createCallAgent(tokenCredentialB);
const deviceManager2 = await callClient2.getDeviceManager();

// Join the call with your first CallAgent
const camera1 = await deviceManager1.getCameras()[0];
const callObj1 = callAgent1.join({ groupId: ‘123’}, { videoOptions: { localVideoStreams: [new LocalVideoStream(camera1)] } });

// Join the same call with your second CallAgent and make it use a different camera
const camera2 = (await deviceManager2.getCameras()).filter((camera) => { return camera !== camera1 })[0];
const callObj2 = callAgent2.join({ groupId: '123' }, { videoOptions: { localVideoStreams: [new LocalVideoStream(camera2)] } });

//Mute the microphone and speakers of your second CallAgent’s Call, so that there is no echos/noises.
await callObj2.muteIncomingAudio();
await callObj2.mute();

제한 사항:

  • 서로 다른 ID를 가진 두 개의 서로 다른 호출 에이전트를 사용하여 이 작업을 수행해야 하므로 코드 조각에는 각각 고유한 Call 개체가 있는 두 개의 호출 에이전트가 표시됩니다.
  • 코드 예제에서 두 CallAgent는 동일한 호출(동일한 호출 ID)에 조인합니다. 각 에이전트와 서로 다른 통화에 참가하고 한 통화에서 하나의 비디오와 다른 통화의 다른 비디오를 보낼 수도 있습니다.
  • 두 CallAgent 모두에서 동일한 카메라를 보내는 것은 지원되지 않습니다. 두 개의 서로 다른 카메라여야 합니다.
  • 하나의 CallAgent를 사용하여 두 개의 서로 다른 카메라를 보내는 것은 현재 지원되지 않습니다.
  • macOS Safari에서 배경 흐림 비디오 효과(원본 @azure/communication-effects))는 한 카메라에만 적용할 수 있으며 동시에 적용할 수는 없습니다.

SDK 설치

프로젝트 수준 build.gradle 파일을 찾아 아래 및 아래의 리포지 allprojectsbuildscript 토리 목록에 추가 mavenCentral() 합니다.

buildscript {
    repositories {
    ...
        mavenCentral()
    ...
    }
}
allprojects {
    repositories {
    ...
        mavenCentral()
    ...
    }
}

그런 다음, 모듈 수준 build.gradle 파일에서 섹션에 다음 줄을 dependencies 추가합니다.

dependencies {
    ...
    implementation 'com.azure.android:azure-communication-calling:1.0.0'
    ...
}

필요한 개체 초기화

인스턴스를 CallAgent 만들려면 인스턴스에서 메서드를 createCallAgent 호출해야 합니다 CallClient . 이 호출은 인스턴스 개체를 CallAgent 비동기적으로 반환합니다.

이 메서드는 createCallAgent 액세스 토큰을 캡슐화하는 인수로 사용합니다CommunicationUserCredential.

액세스 DeviceManager하려면 먼저 인스턴스를 callAgent 만들어야 합니다. 그런 다음 메서드를 사용하여 .를 CallClient.getDeviceManager 가져올 DeviceManager수 있습니다.

String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential).get();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();

호출자의 표시 이름을 설정하려면 다음과 같은 대체 방법을 사용합니다.

String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgentOptions callAgentOptions = new CallAgentOptions();
callAgentOptions.setDisplayName("Alice Bob");
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential, callAgentOptions).get();

디바이스 관리

통화로 비디오 사용을 시작하려면 디바이스를 관리하는 방법을 알아야 합니다. 디바이스를 사용하면 오디오 및 비디오를 통화로 전송하는 것을 제어할 수 있습니다.

DeviceManager를 사용하여 오디오/비디오 스트림을 전송하는 호출에 사용 가능한 로컬 디바이스를 열거할 수 있습니다. 또한 네이티브 브라우저 API를 사용하여 사용자의 마이크 및 카메라에 액세스할 수 있는 권한을 요청할 수 있습니다.

callClient.getDeviceManager() 메서드를 호출하여 deviceManager에 액세스할 수 있습니다.

Context appContext = this.getApplicationContext();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();

로컬 디바이스 열거

로컬 디바이스에 액세스하려면 디바이스 관리자에서 열거 메서드를 사용합니다. 열거형은 동기 작업입니다.

//  Get a list of available video devices for use.
List<VideoDeviceInfo> localCameras = deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]

로컬 카메라 미리 보기

로컬 카메라에서 스트림 렌더링을 시작하고 사용할 DeviceManagerRenderer 수 있습니다. 이 스트림은 다른 참가자에게 전송되지 않습니다. 로컬 미리 보기 피드입니다. 비동기 작업입니다.

VideoDeviceInfo videoDevice = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentVideoStream = new LocalVideoStream(videoDevice, appContext);

LocalVideoStream[] localVideoStreams = new LocalVideoStream[1];
localVideoStreams[0] = currentVideoStream;

VideoOptions videoOptions = new VideoOptions(localVideoStreams);

RenderingOptions renderingOptions = new RenderingOptions(ScalingMode.Fit);
VideoStreamRenderer previewRenderer = new VideoStreamRenderer(currentVideoStream, appContext);

VideoStreamRendererView uiView = previewRenderer.createView(renderingOptions);

// Attach the uiView to a viewable location on the app at this point
layout.addView(uiView);

비디오 카메라로 1:1 통화

Warning

현재는 발신 로컬 비디오 스트림 하나만 지원됩니다. 비디오를 통해 전화를 걸려면 deviceManagergetCameras API를 사용하여 로컬 카메라를 열거해야 합니다. 원하는 카메라를 선택하면 인스턴스를 생성 LocalVideoStream 하고 배열의 항목 localVideoStream 으로 메서드에 videoOptions 전달하는 데 call 사용합니다. 전화가 연결되면 선택한 카메라에서 다른 참가자로 비디오 스트림을 자동으로 보내기 시작합니다.

참고 항목

개인 정보 보호 문제로 인해 비디오가 로컬로 미리 보기되지 않는 경우 통화에 공유되지 않습니다. 자세한 내용은 로컬 카메라 미리 보기를 참조하세요.

VideoDeviceInfo desiredCamera = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentVideoStream = new LocalVideoStream(desiredCamera, appContext);

LocalVideoStream[] localVideoStreams = new LocalVideoStream[1];
localVideoStreams[0] = currentVideoStream;

VideoOptions videoOptions = new VideoOptions(localVideoStreams);

// Render a local preview of video so the user knows that their video is being shared
Renderer previewRenderer = new VideoStreamRenderer(currentVideoStream, appContext);
View uiView = previewRenderer.createView(new CreateViewOptions(ScalingMode.FIT));

// Attach the uiView to a viewable location on the app at this point
layout.addView(uiView);

CommunicationUserIdentifier[] participants = new CommunicationUserIdentifier[]{ new CommunicationUserIdentifier("<acs user id>") };

StartCallOptions startCallOptions = new StartCallOptions();
startCallOptions.setVideoOptions(videoOptions);

Call call = callAgent.startCall(context, participants, startCallOptions);

로컬 비디오 보내기 시작 및 중지

비디오를 시작하려면 개체에서 API deviceManager 를 사용하여 getCameraList 카메라를 열거해야 합니다. 그런 다음, 원하는 카메라를 전달하는 새 인스턴스 LocalVideoStream 를 만들고 API에 startVideo 인수로 전달합니다.

VideoDeviceInfo desiredCamera = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentLocalVideoStream = new LocalVideoStream(desiredCamera, appContext);

VideoOptions videoOptions = new VideoOptions(currentLocalVideoStream);

Future startVideoFuture = call.startVideo(appContext, currentLocalVideoStream);
startVideoFuture.get();

비디오 전송을 성공적으로 시작하면 호출 인스턴스의 localVideoStreams 컬렉션에 LocalVideoStream 인스턴스가 추가됩니다.

List<LocalVideoStream> videoStreams = call.getLocalVideoStreams();
LocalVideoStream currentLocalVideoStream = videoStreams.get(0); // Please make sure there are VideoStreams in the list before calling get(0).

로컬 비디오를 중지하려면 컬렉션에서 사용할 수 있는 인스턴스를 LocalVideoStreamlocalVideoStreams 전달합니다.

call.stopVideo(appContext, currentLocalVideoStream).get();

인스턴스에서 호출 switchSource 하여 비디오를 보내는 동안 다른 카메라 디바이스로 LocalVideoStream 전환할 수 있습니다.

currentLocalVideoStream.switchSource(source).get();

원격 참가자 비디오 스트림 렌더링

원격 참가자의 비디오 스트림과 화면 공유 스트림을 나열하려면 videoStreams 컬렉션을 검사합니다.

List<RemoteParticipant> remoteParticipants = call.getRemoteParticipants();
RemoteParticipant remoteParticipant = remoteParticipants.get(0); // Please make sure there are remote participants in the list before calling get(0).

List<RemoteVideoStream> remoteStreams = remoteParticipant.getVideoStreams();
RemoteVideoStream remoteParticipantStream = remoteStreams.get(0); // Please make sure there are video streams in the list before calling get(0).

MediaStreamType streamType = remoteParticipantStream.getType(); // of type MediaStreamType.Video or MediaStreamType.ScreenSharing

원격 참가자에서 렌더링 RemoteVideoStream 하려면 이벤트를 구독해야 합니다 OnVideoStreamsUpdated .

이벤트 내에서 속성이 true로 변경 isAvailable 되면 원격 참가자가 현재 스트림을 보내고 있음을 나타냅니다. 이 경우 새 인스턴스를 Renderer만든 다음, 비동 createView 기 API를 사용하여 새 RendererView 인스턴스를 만들고 애플리케이션의 UI에 있는 모든 위치에 연결 view.target 합니다.

원격 스트림의 가용성이 변경될 때마다 전체 렌더러, 특정 RendererView 또는 유지를 삭제하도록 선택할 수 있지만 이로 인해 빈 비디오 프레임이 표시됩니다.

VideoStreamRenderer remoteVideoRenderer = new VideoStreamRenderer(remoteParticipantStream, appContext);
VideoStreamRendererView uiView = remoteVideoRenderer.createView(new RenderingOptions(ScalingMode.FIT));
layout.addView(uiView);

remoteParticipant.addOnVideoStreamsUpdatedListener(e -> onRemoteParticipantVideoStreamsUpdated(p, e));

void onRemoteParticipantVideoStreamsUpdated(RemoteParticipant participant, RemoteVideoStreamsEvent args) {
    for(RemoteVideoStream stream : args.getAddedRemoteVideoStreams()) {
        if(stream.getIsAvailable()) {
            startRenderingVideo();
        } else {
            renderer.dispose();
        }
    }
}

원격 비디오 스트림 속성

원격 비디오 스트림에는 몇 가지 속성이 있습니다.

  • Id - 원격 비디오 스트림의 ID
int id = remoteVideoStream.getId();
  • MediaStreamType - '비디오' 또는 'ScreenSharing'일 수 있습니다.
MediaStreamType type = remoteVideoStream.getMediaStreamType();
  • isAvailable - 원격 참가자 엔드포인트가 스트림을 적극적으로 보내는지 나타냅니다.
boolean availability = remoteVideoStream.isAvailable();

렌더러 메서드 및 속성

API를 따르는 렌더러 개체

  • 나중에 애플리케이션 UI에서 연결하여 원격 비디오 스트림을 렌더링할 수 있는 VideoStreamRendererView 인스턴스를 만듭니다.
// Create a view for a video stream
VideoStreamRendererView.createView()
  • 렌더러 및 이 렌더러와 연결된 모든 VideoStreamRendererView를 삭제합니다. UI에서 연결된 모든 뷰를 제거한 경우 호출됩니다.
VideoStreamRenderer.dispose()
  • StreamSize - 원격 비디오 스트림의 크기(너비/높이)
StreamSize renderStreamSize = VideoStreamRenderer.getSize();
int width = renderStreamSize.getWidth();
int height = renderStreamSize.getHeight();

RendererView 메서드 및 속성

만들 VideoStreamRendererView 때 이 보기에 ScalingMode 적용할 속성 및 mirrored 속성을 지정할 수 있습니다. 크기 조정 모드는 'CROP' 중 하나일 수 있습니다. | 'FIT'

VideoStreamRenderer remoteVideoRenderer = new VideoStreamRenderer(remoteVideoStream, appContext);
VideoStreamRendererView rendererView = remoteVideoRenderer.createView(new CreateViewOptions(ScalingMode.Fit));

이렇게 만든 RendererView는 다음 코드 조각을 사용하여 애플리케이션 UI에 연결할 수 있습니다.

layout.addView(rendererView);

나중에 RendererView 개체에서 ScalingMode.CROP | ScalingMode.FIT 중 하나를 인수로 사용하고 updateScalingMode API를 호출하여 스케일링 모드를 업데이트할 수 있습니다.

// Update the scale mode for this view.
rendererView.updateScalingMode(ScalingMode.CROP)

시스템 설정

Xcode 프로젝트 만들기

Xcode에서 새 iOS 프로젝트를 만들고 단일 보기 앱 템플릿을 선택합니다. 이 빠른 시작에서는 SwiftUI 프레임워크를 사용하므로 언어를 Swift로 설정하고 인터페이스SwiftUI설정해야 합니다.

이 빠른 시작 중에는 테스트를 만들지 않을 것입니다. 테스트 포함 검사 상자를 자유롭게 지웁니다.

Screenshot that shows the window for creating a project within Xcode.

CocoaPods를 사용하여 패키지 및 종속성 설치

  1. 다음 예제와 같이 애플리케이션에 대한 Podfile을 만듭니다.

    platform :ios, '13.0'
    use_frameworks!
    target 'AzureCommunicationCallingSample' do
        pod 'AzureCommunicationCalling', '~> 1.0.0'
    end
    
  2. pod install를 실행합니다.

  3. Xcode를 사용하여 엽니다 .xcworkspace .

마이크에 대한 액세스 요청

디바이스의 마이크에 액세스하려면 을 사용하여 NSMicrophoneUsageDescription앱의 정보 속성 목록을 업데이트해야 합니다. 연결된 값을 시스템에서 사용자의 액세스를 요청하는 데 사용하는 대화 상자에 포함될 문자열로 설정합니다.

프로젝트 트리의 Info.plist 항목을 마우스 오른쪽 단추로 클릭한 다음 소스 코드>열기를 선택합니다. 최상위 <dict> 섹션에 다음 줄을 추가한 다음, 파일을 저장합니다.

<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for VOIP calling.</string>

앱 프레임워크 설정

프로젝트의 ContentView.swift 파일을 엽니다. import 파일 맨 위에 선언을 추가하여 라이브러리를 AzureCommunicationCalling 가져옵니다. 추가로 AVFoundation을 가져옵니다. 코드에서 오디오 권한 요청에 필요합니다.

import AzureCommunicationCalling
import AVFoundation

CallAgent 초기화

CallAgent에서 CallClient 인스턴스를 만들려면 초기화된 후 비동기적으로 callClient.createCallAgent 개체를 반환하는 CallAgent 메서드를 사용해야 합니다.

호출 클라이언트를 만들려면 개체를 전달합니다.CommunicationTokenCredential

import AzureCommunication

let tokenString = "token_string"
var userCredential: CommunicationTokenCredential?
do {
    let options = CommunicationTokenRefreshOptions(initialToken: token, refreshProactively: true, tokenRefresher: self.fetchTokenSync)
    userCredential = try CommunicationTokenCredential(withOptions: options)
} catch {
    updates("Couldn't created Credential object", false)
    initializationDispatchGroup!.leave()
    return
}

// tokenProvider needs to be implemented by Contoso, which fetches a new token
public func fetchTokenSync(then onCompletion: TokenRefreshOnCompletion) {
    let newToken = self.tokenProvider!.fetchNewToken()
    onCompletion(newToken, nil)
}

만든 개체를 CommunicationTokenCredentialCallClient전달하고 표시 이름을 설정합니다.

self.callClient = CallClient()
let callAgentOptions = CallAgentOptions()
options.displayName = " iOS Azure Communication Services User"

self.callClient!.createCallAgent(userCredential: userCredential!,
    options: callAgentOptions) { (callAgent, error) in
        if error == nil {
            print("Create agent succeeded")
            self.callAgent = callAgent
        } else {
            print("Create agent failed")
        }
})

디바이스 관리

통화로 비디오 사용을 시작하려면 디바이스를 관리하는 방법을 알아야 합니다. 디바이스를 사용하면 오디오 및 비디오를 통화로 전송하는 것을 제어할 수 있습니다.

DeviceManager를 사용하여 오디오 또는 비디오 스트림을 전송하는 호출에 사용 가능한 로컬 디바이스를 열거할 수 있습니다. 또한 사용자가 마이크 또는 카메라에 액세스할 수 있는 권한을 요청할 수 있습니다. callClient 개체에서 deviceManager에 액세스할 수 있습니다.

self.callClient!.getDeviceManager { (deviceManager, error) in
        if (error == nil) {
            print("Got device manager instance")
            self.deviceManager = deviceManager
        } else {
            print("Failed to get device manager instance")
        }
    }

로컬 디바이스 열거

로컬 디바이스에 액세스하려면 디바이스 관리자에서 열거형 메서드를 사용할 수 있습니다. 열거형은 동기 작업입니다.

// enumerate local cameras
var localCameras = deviceManager.cameras // [VideoDeviceInfo, VideoDeviceInfo...]

로컬 카메라 미리 보기 가져오기

로컬 카메라에서 스트림 렌더링을 시작하는 데 사용할 Renderer 수 있습니다. 이 스트림은 다른 참가자에게 전송되지 않습니다. 로컬 미리 보기 피드입니다. 비동기 작업입니다.

let camera: VideoDeviceInfo = self.deviceManager!.cameras.first!
let localVideoStream = LocalVideoStream(camera: camera)
let localRenderer = try! VideoStreamRenderer(localVideoStream: localVideoStream)
self.view = try! localRenderer.createView()

로컬 카메라 미리 보기 속성 가져오기

렌더러에는 렌더링을 제어할 수 있는 속성 및 메서드 집합이 있습니다.

// Constructor can take in LocalVideoStream or RemoteVideoStream
let localRenderer = VideoStreamRenderer(localVideoStream:localVideoStream)
let remoteRenderer = VideoStreamRenderer(remoteVideoStream:remoteVideoStream)

// [StreamSize] size of the rendering view
localRenderer.size

// [VideoStreamRendererDelegate] an object you provide to receive events from this Renderer instance
localRenderer.delegate

// [Synchronous] create view
try! localRenderer.createView()

// [Synchronous] create view with rendering options
try! localRenderer!.createView(withOptions: CreateViewOptions(scalingMode: ScalingMode.fit))

// [Synchronous] dispose rendering view
localRenderer.dispose()

비디오로 1:1 통화

디바이스 관리자 인스턴스를 얻으려면 디바이스 관리에 대한 섹션을 참조하세요.

let firstCamera = self.deviceManager!.cameras.first
self.localVideoStreams = [LocalVideoStream]()
self.localVideoStreams!.append(LocalVideoStream(camera: firstCamera!))
let videoOptions = VideoOptions(localVideoStreams: self.localVideoStreams!)

let startCallOptions = StartCallOptions()
startCallOptions.videoOptions = videoOptions

let callee = CommunicationUserIdentifier('UserId')
self.callAgent?.startCall(participants: [callee], options: startCallOptions) { (call, error) in
    if error == nil {
        print("Successfully started outgoing video call")
        self.call = call
    } else {
        print("Failed to start outgoing video call")
    }
}

원격 참가자 비디오 스트림 렌더링

원격 참가자는 통화 중에 비디오 또는 화면 공유를 시작할 수 있습니다.

원격 참가자의 비디오 공유 또는 화면 공유 스트림 처리

원격 참가자의 스트림을 나열하려면 컬렉션을 검사 videoStreams 합니다.

var remoteParticipantVideoStream = call.remoteParticipants[0].videoStreams[0]

원격 비디오 스트림 속성 가져오기

var type: MediaStreamType = remoteParticipantVideoStream.type // 'MediaStreamTypeVideo'
var isAvailable: Bool = remoteParticipantVideoStream.isAvailable // indicates if remote stream is available
var id: Int = remoteParticipantVideoStream.id // id of remoteParticipantStream

원격 참가자 스트림 렌더링

원격 참가자 스트림 렌더링을 시작하려면 다음 코드를 사용합니다.

let renderer = VideoStreamRenderer(remoteVideoStream: remoteParticipantVideoStream)
let targetRemoteParticipantView = renderer?.createView(withOptions: CreateViewOptions(scalingMode: ScalingMode.crop))
// To update the scaling mode later
targetRemoteParticipantView.update(scalingMode: ScalingMode.fit)

원격 비디오 렌더러 메서드 및 속성 가져오기

// [Synchronous] dispose() - dispose renderer and all `RendererView` associated with this renderer. To be called when you have removed all associated views from the UI.
remoteVideoRenderer.dispose()

시스템 설정

Visual Studio 프로젝트 만들기

UWP 앱의 경우 Visual Studio 2022에서 새 빈 앱(유니버설 Windows) 프로젝트를 만듭니다. 프로젝트 이름을 입력한 후에는 10.0.17763.0 이후의 Windows SDK를 자유롭게 선택할 수 있습니다.

WinUI 3 앱의 경우 빈 앱 패키지(데스크톱의 WinUI 3) 템플릿을 사용하여 새 프로젝트를 만들어 단일 페이지 WinUI 3 앱을 설정합니다. Windows 앱 SDK 버전 1.3 이상이 필요합니다.

NuGet 패키지 관리자 사용하여 패키지 및 종속성 설치

Calling SDK API와 라이브러리는 NuGet 패키지를 통해 공개적으로 사용할 수 있습니다.

다음 단계에서는 통화 SDK NuGet 패키지를 찾고, 다운로드하고, 설치하는 방법을 예시합니다.

  1. 도구 NuGet 패키지 관리자Manage NuGet Packages for Solution을> 선택하여 NuGet 패키지 관리자> 엽니다.
  2. 찾아보기를 선택한 다음 검색 상자에 입력 Azure.Communication.Calling.WindowsClient 합니다.
  3. 시험판 포함 검사 상자가 선택되어 있는지 확인합니다.
  4. Azure.Communication.Calling.WindowsClient 패키지를 선택한 다음 1.4.0-beta.1 이상 버전을 선택합니다Azure.Communication.Calling.WindowsClient.
  5. 오른쪽 탭에서 Communication Services 프로젝트에 해당하는 검사box를 선택합니다.
  6. 설치 단추를 선택합니다.

마이크에 대한 액세스 요청

앱을 제대로 실행하려면 카메라에 대한 액세스 권한이 필요합니다. UWP 앱의 앱 매니페스트 파일에서 카메라 기능을 선언해야 합니다.

다음 단계는 이를 달성하는 방법을 예시합니다.

  1. 패널에서 Solution Explorer 확장이 있는 .appxmanifest 파일을 두 번 클릭합니다.
  2. 탭을 Capabilities 클릭합니다.
  3. Camera 기능 목록에서 검사 상자를 선택합니다.

통화를 배치하고 끊을 UI 단추 만들기

이 간단한 샘플 앱에는 두 개의 단추가 포함되어 있습니다. 하나는 전화를 걸고 다른 하나는 배치 된 전화를 끊습니다. 다음 단계에서는 앱에 이러한 단추를 추가하는 방법을 안내합니다.

  1. Solution Explorer 패널에서 MainPage.xaml(UWP의 경우) 또는 MainWindows.xaml(WinUI 3의 경우)이라는 파일을 두 번 클릭합니다.
  2. 중앙 패널의 UI 미리 보기에서 XAML 코드를 찾습니다.
  3. 다음 발췌에 따라 XAML 코드를 수정합니다.
<TextBox x:Name="CalleeTextBox" PlaceholderText="Who would you like to call?" />
<StackPanel>
    <Button x:Name="CallButton" Content="Start/Join call" Click="CallButton_Click" />
    <Button x:Name="HangupButton" Content="Hang up" Click="HangupButton_Click" />
</StackPanel>

SDK API 호출을 사용하여 앱 설정

호출 SDK API는 두 개의 서로 다른 네임스페이스에 있습니다. 다음 단계에서는 Visual Studio의 Intellisense가 코드 개발을 지원할 수 있도록 이러한 네임스페이스에 대해 C# 컴파일러에 알릴 수 있습니다.

  1. Solution Explorer 패널에서 MainPage.xaml(UWP의 경우) 또는 MainWindows.xaml(WinUI 3의 경우)이라는 파일의 왼쪽에 있는 화살표를 클릭합니다.
  2. MainPage.xaml.cs 또는 MainWindows.xaml.cs라는 파일을 두 번 클릭합니다.
  3. 현재 using 문의 맨 아래에 다음 명령을 추가합니다.
using Azure.Communication.Calling.WindowsClient;

유지 MainPage.xaml.cs 하거나 MainWindows.xaml.cs 엽니다. 다음 단계에서는 더 많은 코드를 추가합니다.

앱 상호 작용 허용

이전에 추가된 UI 단추는 배치 CommunicationCall된 단추 위에 작동해야 합니다. 즉, CommunicationCall 데이터 멤버가 MainPage 또는 MainWindow 클래스에 추가되어야 합니다. 또한 비동기 작업을 만드는 CallAgent 데 성공 CallAgent 하려면 데이터 멤버도 동일한 클래스에 추가해야 합니다.

다음 데이터 멤버를 또는 클래스에 MainPage 추가합니다 MainWindow .

CallAgent callAgent;
CommunicationCall call;

단추 처리기 만들기

이전에는 XAML 코드에 두 개의 UI 단추가 추가되었습니다. 다음 코드에서는 사용자가 단추를 선택하면 실행될 처리기를 추가합니다. 다음 코드는 이전 섹션의 데이터 멤버 다음에 추가해야 합니다.

private async void CallButton_Click(object sender, RoutedEventArgs e)
{
    // Start call
}

private async void HangupButton_Click(object sender, RoutedEventArgs e)
{
    // End the current call
}

개체 모델

다음 클래스 및 인터페이스는 UWP용 Azure Communication Services 통화 클라이언트 라이브러리의 주요 기능 중 일부를 처리합니다.

이름 설명
CallClient CallClient 호출 클라이언트 라이브러리에 대한 기본 진입점입니다.
CallAgent 호출 CallAgent 을 시작하고 조인하는 데 사용됩니다.
CommunicationCall 배치 CommunicationCall 되거나 조인된 호출을 관리하는 데 사용됩니다.
CommunicationTokenCredential 토큰 CommunicationTokenCredential 자격 증명으로 사용되어 인스턴스화합니다 CallAgent.
CallAgentOptions CallAgentOptions 호출자를 식별하는 정보가 포함되어 있습니다.
HangupOptions 이 알림은 HangupOptions 모든 참가자에게 통화를 종료해야 하는지를 알 수 있습니다.

비디오 스키마 처리기 등록

XAML의 MediaElement 또는 MediaPlayerElement와 같은 UI 구성 요소는 로컬 및 원격 비디오 피드를 렌더링하기 위한 구성을 등록하는 앱이 필요합니다. 다음의 태그 사이에 Package 다음 콘텐츠를 추가합니다.Package.appxmanifest

<Extensions>
    <Extension Category="windows.activatableClass.inProcessServer">
        <InProcessServer>
            <Path>RtmMvrUap.dll</Path>
            <ActivatableClass ActivatableClassId="VideoN.VideoSchemeHandler" ThreadingModel="both" />
        </InProcessServer>
    </Extension>
</Extensions>

CallAgent 초기화

인스턴스CallClientCallAgent 만들려면 개체가 초기화되면 비동기적으로 반환 CallAgent 하는 메서드를 사용해야 CallClient.CreateCallAgentAsync 합니다.

만들 CallAgent려면 개체와 개체를 CallTokenCredentialCallAgentOptions 전달해야 합니다. 잘못된 형식의 토큰이 CallTokenCredential 전달되면 throw된다는 점을 명심하세요.

앱 초기화 시 호출할 도우미 함수 및 내부에 다음 코드를 추가해야 합니다.

var callClient = new CallClient();
this.deviceManager = await callClient.GetDeviceManagerAsync();

var tokenCredential = new CallTokenCredential("<AUTHENTICATION_TOKEN>");
var callAgentOptions = new CallAgentOptions()
{
    DisplayName = "<DISPLAY_NAME>"
};

this.callAgent = await callClient.CreateCallAgentAsync(tokenCredential, callAgentOptions);
this.callAgent.CallsUpdated += Agent_OnCallsUpdatedAsync;
this.callAgent.IncomingCallReceived += Agent_OnIncomingCallAsync;

리소스에 <AUTHENTICATION_TOKEN> 유효한 자격 증명 토큰을 사용하여 변경합니다. 자격 증명 토큰원본으로 지정해야 하는 경우 사용자 액세스 토큰 설명서를 참조하세요.

비디오 카메라로 1:1 통화

이제 개체를 CallAgent 만드는 데 필요한 준비가 되었습니다. 비동기적으로 영상 통화를 만들고 CallAgent 배치해야 할 때입니다.

private async void CallButton_Click(object sender, RoutedEventArgs e)
{
    var callString = CalleeTextBox.Text.Trim();

    if (!string.IsNullOrEmpty(callString))
    {
        if (callString.StartsWith("8:")) // 1:1 Azure Communication Services call
        {
            this.call = await StartAcsCallAsync(callString);
        }
    }

    if (this.call != null)
    {
        this.call.RemoteParticipantsUpdated += OnRemoteParticipantsUpdatedAsync;
        this.call.StateChanged += OnStateChangedAsync;
    }
}

private async Task<CommunicationCall> StartAcsCallAsync(string acsCallee)
{
    var options = await GetStartCallOptionsAsynnc();
    var call = await this.callAgent.StartCallAsync( new [] { new UserCallIdentifier(acsCallee) }, options);
    return call;
}

var micStream = new LocalOutgoingAudioStream(); // Create a default local audio stream
var cameraStream = new LocalOutgoingVideoStreamde(this.viceManager.Cameras.FirstOrDefault() as VideoDeviceDetails); // Create a default video stream

private async Task<StartCallOptions> GetStartCallOptionsAsynnc()
{
    return new StartCallOptions() {
        OutgoingAudioOptions = new OutgoingAudioOptions() { IsMuted = true, Stream = micStream  },
        OutgoingVideoOptions = new OutgoingVideoOptions() { Streams = new OutgoingVideoStream[] { cameraStream } }
    };
}

로컬 카메라 미리 보기

필요에 따라 로컬 카메라 미리 보기를 설정할 수 있습니다. 다음을 통해 비디오를 렌더링할 수 있습니다.MediaPlayerElement

<Grid>
    <MediaPlayerElement x:Name="LocalVideo" AutoPlay="True" />
    <MediaPlayerElement x:Name="RemoteVideo" AutoPlay="True" />
</Grid>

로컬 미리 보기 MediaPlayerElement를 초기화하려면 다음을 수행합니다.

private async void CameraList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (cameraStream != null)
    {
        await cameraStream?.StopPreviewAsync();
        if (this.call != null)
        {
            await this.call?.StopVideoAsync(cameraStream);
        }
    }
    var selectedCamerea = CameraList.SelectedItem as VideoDeviceDetails;
    cameraStream = new LocalOutgoingVideoStream(selectedCamerea);

    var localUri = await cameraStream.StartPreviewAsync();
    LocalVideo.Source = MediaSource.CreateFromUri(localUri);

    if (this.call != null) {
        await this.call?.StartVideoAsync(cameraStream);
    }
}

원격 카메라 스트림 렌더링

OnCallsUpdated 이벤트에 대한 응답으로 이벤트 처리기를 설정합니다.

private async void OnCallsUpdatedAsync(object sender, CallsUpdatedEventArgs args)
{
    var removedParticipants = new List<RemoteParticipant>();
    var addedParticipants = new List<RemoteParticipant>();

    foreach(var call in args.RemovedCalls)
    {
        removedParticipants.AddRange(call.RemoteParticipants.ToList<RemoteParticipant>());
    }

    foreach (var call in args.AddedCalls)
    {
        addedParticipants.AddRange(call.RemoteParticipants.ToList<RemoteParticipant>());
    }

    await OnParticipantChangedAsync(removedParticipants, addedParticipants);
}

private async void OnRemoteParticipantsUpdatedAsync(object sender, ParticipantsUpdatedEventArgs args)
{
    await OnParticipantChangedAsync(
        args.RemovedParticipants.ToList<RemoteParticipant>(),
        args.AddedParticipants.ToList<RemoteParticipant>());
}

private async Task OnParticipantChangedAsync(IEnumerable<RemoteParticipant> removedParticipants, IEnumerable<RemoteParticipant> addedParticipants)
{
    foreach (var participant in removedParticipants)
    {
        foreach(var incomingVideoStream in  participant.IncomingVideoStreams)
        {
            var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
            if (remoteVideoStream != null)
            {
                await remoteVideoStream.StopPreviewAsync();
            }
        }
        participant.VideoStreamStateChanged -= OnVideoStreamStateChanged;
    }

    foreach (var participant in addedParticipants)
    {
        participant.VideoStreamStateChanged += OnVideoStreamStateChanged;
    }
}

private void OnVideoStreamStateChanged(object sender, VideoStreamStateChangedEventArgs e)
{
    CallVideoStream callVideoStream = e.CallVideoStream;

    switch (callVideoStream.StreamDirection)
    {
        case StreamDirection.Outgoing:
            OnOutgoingVideoStreamStateChanged(callVideoStream as OutgoingVideoStream);
            break;
        case StreamDirection.Incoming:
            OnIncomingVideoStreamStateChanged(callVideoStream as IncomingVideoStream);
            break;
    }
}

다음에서 원격 비디오 스트림 렌더링을 시작합니다.MediaPlayerElement

private async void OnIncomingVideoStreamStateChanged(IncomingVideoStream incomingVideoStream)
{
    switch (incomingVideoStream.State)
    {
        case VideoStreamState.Available:
            {
                switch (incomingVideoStream.Kind)
                {
                    case VideoStreamKind.RemoteIncoming:
                        var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
                        var uri = await remoteVideoStream.StartPreviewAsync();

                        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                        {
                            RemoteVideo.Source = MediaSource.CreateFromUri(uri);
                        });

                        /* Or WinUI 3
                        this.DispatcherQueue.TryEnqueue(() => {
                            RemoteVideo.Source = MediaSource.CreateFromUri(uri);
                            RemoteVideo.MediaPlayer.Play();
                        });
                        */

                        break;

                    case VideoStreamKind.RawIncoming:
                        break;
                }

                break;
            }
        case VideoStreamState.Started:
            break;
        case VideoStreamState.Stopping:
            break;
        case VideoStreamState.Stopped:
            if (incomingVideoStream.Kind == VideoStreamKind.RemoteIncoming)
            {
                var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
                await remoteVideoStream.StopPreviewAsync();
            }
            break;
        case VideoStreamState.NotAvailable:
            break;
    }
}

통화 종료

호출이 완료되면 HangupAsync 개체의 메서드를 CommunicationCall 사용하여 호출을 중단해야 합니다.

또한 인스턴스를 HangupOptions 사용하여 모든 참가자에게 통화를 종료해야 하는지를 알려야 합니다.

다음 코드는 HangupButton_Click 내에 추가되어야 합니다.

var call = this.callAgent?.Calls?.FirstOrDefault();
if (call != null)
{
    var call = this.callAgent?.Calls?.FirstOrDefault();
    if (call != null)
    {
        foreach (var localVideoStream in call.OutgoingVideoStreams)
        {
            await call.StopVideoAsync(localVideoStream);
        }

        try
        {
            if (cameraStream != null)
            {
                await cameraStream.StopPreviewAsync();
            }

            await call.HangUpAsync(new HangUpOptions() { ForEveryone = false });
        }
        catch(Exception ex) 
        { 
            var errorCode = unchecked((int)(0x0000FFFFU & ex.HResult));
            if (errorCode != 98) // Sample error code, sam_status_failed_to_hangup_for_everyone (98)
            {
                throw;
            }
        }
    }
}

코드 실행

Visual Studio에서 앱을 x64x86 빌드하는지 또는 ARM64적중 F5 하여 앱 실행을 시작하는지 확인합니다. 그런 다음 단추를 클릭하여 CommunicationCall 정의된 호출 수신자를 호출합니다.

앱이 처음 실행되면 시스템에서 사용자에게 마이크에 대한 액세스 권한을 부여하라는 메시지를 표시합니다.

다음 단계