통화 중 비디오 관리
Azure Communication Services SDKS를 사용하여 화상 통화를 관리하는 방법을 알아봅니다. 통화 내에서 비디오 수신 및 보내기를 관리하는 방법을 알아봅니다.
필수 조건
- 활성 구독이 있는 Azure 계정. 체험 계정을 만듭니다.
- 배포된 Communication Services 리소스. Communication Services 리소스 만들기
- 호출 클라이언트를 사용하도록 설정하는 사용자 액세스 토큰입니다. 자세한 내용은 액세스 토큰 만들기 및 관리를 참조하세요.
- 선택 사항: 빠른 시작을 완료하여 애플리케이션에 음성 통화를 추가합니다.
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;
로컬 카메라 미리 보기
로컬 카메라에서 스트림 렌더링을 시작하고 사용할 deviceManager
VideoStreamRenderer
수 있습니다. 이 스트림은 다른 참가자에게 전송되지 않습니다. 로컬 미리 보기 피드입니다.
// 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()
시작하는 경우 이 메서드는 aSourceUnavailableError
를 throw하고cameraStartFiled
사용자 연결 진단은 true로 설정됩니다. - 메서드
cameraStartFailed
를localVideoStream.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
를 만들 때 scalingMode
및 isMirrored
속성을 지정할 수 있습니다. scalingMode
는 Stretch
, 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 파일을 찾아 아래 및 아래의 리포지 allprojects
buildscript
토리 목록에 추가 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...]
로컬 카메라 미리 보기
로컬 카메라에서 스트림 렌더링을 시작하고 사용할 DeviceManager
Renderer
수 있습니다. 이 스트림은 다른 참가자에게 전송되지 않습니다. 로컬 미리 보기 피드입니다. 비동기 작업입니다.
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
현재는 발신 로컬 비디오 스트림 하나만 지원됩니다. 비디오를 통해 전화를 걸려면 deviceManager
getCameras
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).
로컬 비디오를 중지하려면 컬렉션에서 사용할 수 있는 인스턴스를 LocalVideoStream
localVideoStreams
전달합니다.
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로 설정해야 합니다.
이 빠른 시작 중에는 테스트를 만들지 않을 것입니다. 테스트 포함 검사 상자를 자유롭게 지웁니다.
CocoaPods를 사용하여 패키지 및 종속성 설치
다음 예제와 같이 애플리케이션에 대한 Podfile을 만듭니다.
platform :ios, '13.0' use_frameworks! target 'AzureCommunicationCallingSample' do pod 'AzureCommunicationCalling', '~> 1.0.0' end
pod install
를 실행합니다.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)
}
만든 개체를 CommunicationTokenCredential
CallClient
전달하고 표시 이름을 설정합니다.
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 패키지를 찾고, 다운로드하고, 설치하는 방법을 예시합니다.
- 도구 NuGet 패키지 관리자Manage NuGet Packages for Solution을> 선택하여 NuGet 패키지 관리자> 엽니다.
- 찾아보기를 선택한 다음 검색 상자에 입력
Azure.Communication.Calling.WindowsClient
합니다. - 시험판 포함 검사 상자가 선택되어 있는지 확인합니다.
Azure.Communication.Calling.WindowsClient
패키지를 선택한 다음 1.4.0-beta.1 이상 버전을 선택합니다Azure.Communication.Calling.WindowsClient
.- 오른쪽 탭에서 Communication Services 프로젝트에 해당하는 검사box를 선택합니다.
- 설치 단추를 선택합니다.
마이크에 대한 액세스 요청
앱을 제대로 실행하려면 카메라에 대한 액세스 권한이 필요합니다. UWP 앱의 앱 매니페스트 파일에서 카메라 기능을 선언해야 합니다.
다음 단계는 이를 달성하는 방법을 예시합니다.
- 패널에서
Solution Explorer
확장이 있는.appxmanifest
파일을 두 번 클릭합니다. - 탭을
Capabilities
클릭합니다. Camera
기능 목록에서 검사 상자를 선택합니다.
통화를 배치하고 끊을 UI 단추 만들기
이 간단한 샘플 앱에는 두 개의 단추가 포함되어 있습니다. 하나는 전화를 걸고 다른 하나는 배치 된 전화를 끊습니다. 다음 단계에서는 앱에 이러한 단추를 추가하는 방법을 안내합니다.
Solution Explorer
패널에서MainPage.xaml
(UWP의 경우) 또는MainWindows.xaml
(WinUI 3의 경우)이라는 파일을 두 번 클릭합니다.- 중앙 패널의 UI 미리 보기에서 XAML 코드를 찾습니다.
- 다음 발췌에 따라 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# 컴파일러에 알릴 수 있습니다.
Solution Explorer
패널에서MainPage.xaml
(UWP의 경우) 또는MainWindows.xaml
(WinUI 3의 경우)이라는 파일의 왼쪽에 있는 화살표를 클릭합니다.MainPage.xaml.cs
또는MainWindows.xaml.cs
라는 파일을 두 번 클릭합니다.- 현재
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 초기화
인스턴스CallClient
를 CallAgent
만들려면 개체가 초기화되면 비동기적으로 반환 CallAgent
하는 메서드를 사용해야 CallClient.CreateCallAgentAsync
합니다.
만들 CallAgent
려면 개체와 개체를 CallTokenCredential
CallAgentOptions
전달해야 합니다. 잘못된 형식의 토큰이 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에서 앱을 x64
x86
빌드하는지 또는 ARM64
적중 F5
하여 앱 실행을 시작하는지 확인합니다. 그런 다음 단추를 클릭하여 CommunicationCall
정의된 호출 수신자를 호출합니다.
앱이 처음 실행되면 시스템에서 사용자에게 마이크에 대한 액세스 권한을 부여하라는 메시지를 표시합니다.