Snabbstart: Ansluta till ett rumssamtal

Förutsättningar

Hämta användaråtkomsttoken

Om du redan har skapat användare och har lagt till dem som deltagare i rummet efter avsnittet "Konfigurera rumsdeltagare" på den här sidan kan du använda dessa användare direkt för att ansluta till rummet.

Annars måste du skapa en användaråtkomsttoken för varje samtalsdeltagare. Lär dig hur du skapar och hanterar användaråtkomsttoken. Du kan också använda Azure CLI och köra kommandot nedan med din anslutningssträng för att skapa en användare och en åtkomsttoken. När användarna har skapats måste du lägga till dem i rummet som deltagare innan de kan gå med i rummet.

az communication identity token issue --scope voip --connection-string "yourConnectionString"

Mer information finns i Använda Azure CLI för att skapa och hantera åtkomsttoken.

Kommentar

Du kan komma åt rum med hjälp av Azure Communication Services-användargränssnittsbiblioteket. Med användargränssnittsbiblioteket kan utvecklare lägga till en samtalsklient som är Rum aktiverad i sitt program med bara några rader kod.

Ansluta till ett rumssamtal

Om du vill följa med i den här snabbstarten kan du ladda ned snabbstarten Room Call på GitHub.

Förutsättningar

  • Du måste ha Node.js 18. Du kan använda installationsprogrammet msi för att installera det.

Konfigurera

Skapa ett nytt Node.js-program

Öppna terminalen eller kommandofönstret skapa en ny katalog för din app och navigera till den.

mkdir calling-rooms-quickstart && cd calling-rooms-quickstart

Kör npm init -y för att skapa en package.json fil med standardinställningar.

npm init -y

Installera -paketet

npm install Använd kommandot för att installera Azure Communication Services Calling SDK för JavaScript.

Viktigt!

Den här snabbstarten använder Azure Communication Services Calling SDK-versionen 1.14.1. Möjligheten att ansluta till ett rumssamtal och visa rollerna för samtalsdeltagare finns i Anropa JavaScript SDK för webbläsare version 1.13.1 och senare.

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

Konfigurera appramverket

Den här snabbstarten använder Webpack för att paketera programtillgångarna. Kör följande kommando för att installera , webpack-cli och webpack-dev-server npm-paketen webpackoch lista dem som utvecklingsberoenden i :package.json

npm install copy-webpack-plugin@^11.0.0 webpack@^5.88.2 webpack-cli@^5.1.4 webpack-dev-server@^4.15.1 --save-dev

Här är koden:

Skapa en index.html fil i rotkatalogen för projektet. Vi använder den här filen för att konfigurera en grundläggande layout som gör att användaren kan ansluta till ett rumsanrop.

<!-- index.html-->
<!DOCTYPE html>
<html>
    <head>
        <title>Azure Communication Services - Rooms Call Sample</title>
        <link rel="stylesheet" type="text/css" href="styles.css"/>
    </head>
    <body>
        <h4>Azure Communication Services - Rooms Call Sample</h4>
        <input id="user-access-token"
            type="text"
            placeholder="User access token"
            style="margin-bottom:1em; width: 500px;"/>
        <button id="initialize-call-agent" type="button">Initialize Call Agent</button>
        <br>
        <br>
        <input id="acs-room-id"
            type="text"
            placeholder="Enter Room Id"
            style="margin-bottom:1em; width: 500px; display: block;"/>
        <button id="join-room-call-button" type="button" disabled="true">Join Room Call</button>
        <button id="hangup-call-button" type="button" disabled="true">Hang up Call</button>
        <button id="start-video-button" type="button" disabled="true">Start Video</button>
        <button id="stop-video-button" type="button" disabled="true">Stop Video</button>
        <br>
        <br>
        <div id="connectedLabel" style="color: #13bb13;" hidden>Room Call is connected!</div>
        <br>
        <div id="remoteVideosGallery" style="width: 40%;" hidden>Remote participants' video streams:</div>
        <br>
        <div id="localVideoContainer" style="width: 30%;" hidden>Local video stream:</div>
        <!-- points to the bundle generated from client.js -->
        <script src="./main.js"></script>
    </body>
</html>

Skapa en fil i rotkatalogen för projektet som anropas index.js för att innehålla programlogik för den här snabbstarten. Lägg till följande kod i index.js:

// Make sure to install the necessary dependencies
const { CallClient, VideoStreamRenderer, LocalVideoStream } = require('@azure/communication-calling');
const { AzureCommunicationTokenCredential } = require('@azure/communication-common');
const { AzureLogger, setLogLevel } = require("@azure/logger");
// Set the log level and output
setLogLevel('verbose');
AzureLogger.log = (...args) => {
    console.log(...args);
};

// Calling web sdk objects
let callAgent;
let deviceManager;
let call;
let localVideoStream;
let localVideoStreamRenderer;

// UI widgets
let userAccessToken = document.getElementById('user-access-token');
let acsRoomId = document.getElementById('acs-room-id');
let initializeCallAgentButton = document.getElementById('initialize-call-agent');
let startCallButton = document.getElementById('join-room-call-button');
let hangUpCallButton = document.getElementById('hangup-call-button');
let startVideoButton = document.getElementById('start-video-button');
let stopVideoButton = document.getElementById('stop-video-button');
let connectedLabel = document.getElementById('connectedLabel');
let remoteVideosGallery = document.getElementById('remoteVideosGallery');
let localVideoContainer = document.getElementById('localVideoContainer');

/**
 * Using the CallClient, initialize a CallAgent instance with a CommunicationUserCredential which enable us to join a rooms call. 
 */
initializeCallAgentButton.onclick = async () => {
    try {
        const callClient = new CallClient(); 
        tokenCredential = new AzureCommunicationTokenCredential(userAccessToken.value.trim());
        callAgent = await callClient.createCallAgent(tokenCredential)
        // Set up a camera device to use.
        deviceManager = await callClient.getDeviceManager();
        await deviceManager.askDevicePermission({ video: true });
        await deviceManager.askDevicePermission({ audio: true });
        
        startCallButton.disabled = false;
        initializeCallAgentButton.disabled = true;
    } catch(error) {
        console.error(error);
    }
}


startCallButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
                
        const roomCallLocator = { roomId: acsRoomId.value.trim() };
        call = callAgent.join(roomCallLocator, { videoOptions });

        // Subscribe to the call's properties and events.
        subscribeToCall(call);
    } catch (error) {
        console.error(error);
    }
}

/**
 * Subscribe to a call obj.
 * Listen for property changes and collection updates.
 */
subscribeToCall = (call) => {
    try {
        // Inspect the initial call.id value.
        console.log(`Call Id: ${call.id}`);
        //Subscribe to call's 'idChanged' event for value changes.
        call.on('idChanged', () => {
            console.log(`Call Id changed: ${call.id}`); 
        });

        // Inspect the initial call.state value.
        console.log(`Call state: ${call.state}`);
        // Subscribe to call's 'stateChanged' event for value changes.
        call.on('stateChanged', async () => {
            console.log(`Call state changed: ${call.state}`);
            if(call.state === 'Connected') {
                connectedLabel.hidden = false;
                startCallButton.disabled = true;
                hangUpCallButton.disabled = false;
                startVideoButton.disabled = false;
                stopVideoButton.disabled = false;
                remoteVideosGallery.hidden = false;
            } else if (call.state === 'Disconnected') {
                connectedLabel.hidden = true;
                startCallButton.disabled = false;
                hangUpCallButton.disabled = true;
                startVideoButton.disabled = true;
                stopVideoButton.disabled = true;
                remoteVideosGallery.hidden = true;
                console.log(`Call ended, call end reason={code=${call.callEndReason.code}, subCode=${call.callEndReason.subCode}}`);
            }   
        });
        call.on('isLocalVideoStartedChanged', () => {
            console.log(`isLocalVideoStarted changed: ${call.isLocalVideoStarted}`);
        });
        console.log(`isLocalVideoStarted: ${call.isLocalVideoStarted}`);
        call.localVideoStreams.forEach(async (lvs) => {
            localVideoStream = lvs;
            await displayLocalVideoStream();
        });
        call.on('localVideoStreamsUpdated', e => {
            e.added.forEach(async (lvs) => {
                localVideoStream = lvs;
                await displayLocalVideoStream();
            });
            e.removed.forEach(lvs => {
               removeLocalVideoStream();
            });
        });
        
        // Inspect the call's current remote participants and subscribe to them.
        call.remoteParticipants.forEach(remoteParticipant => {
            subscribeToRemoteParticipant(remoteParticipant);
        });
        // Subscribe to the call's 'remoteParticipantsUpdated' event to be
        // notified when new participants are added to the call or removed from the call.
        call.on('remoteParticipantsUpdated', e => {
            // Subscribe to new remote participants that are added to the call.
            e.added.forEach(remoteParticipant => {
                subscribeToRemoteParticipant(remoteParticipant)
            });
            // Unsubscribe from participants that are removed from the call
            e.removed.forEach(remoteParticipant => {
                console.log('Remote participant removed from the call.');
            });
        });
    } catch (error) {
        console.error(error);
    }
}

/**
 * Subscribe to a remote participant obj.
 * Listen for property changes and collection udpates.
 */
subscribeToRemoteParticipant = (remoteParticipant) => {
    try {
        // Inspect the initial remoteParticipant.state value.
        console.log(`Remote participant state: ${remoteParticipant.state}`);
        // Subscribe to remoteParticipant's 'stateChanged' event for value changes.
        remoteParticipant.on('stateChanged', () => {
            console.log(`Remote participant state changed: ${remoteParticipant.state}`);
        });

        // Inspect the remoteParticipants's current videoStreams and subscribe to them.
        remoteParticipant.videoStreams.forEach(remoteVideoStream => {
            subscribeToRemoteVideoStream(remoteVideoStream)
        });
        // Subscribe to the remoteParticipant's 'videoStreamsUpdated' event to be
        // notified when the remoteParticiapant adds new videoStreams and removes video streams.
        remoteParticipant.on('videoStreamsUpdated', e => {
            // Subscribe to new remote participant's video streams that were added.
            e.added.forEach(remoteVideoStream => {
                subscribeToRemoteVideoStream(remoteVideoStream)
            });
            // Unsubscribe from remote participant's video streams that were removed.
            e.removed.forEach(remoteVideoStream => {
                console.log('Remote participant video stream was removed.');
            })
        });
    } catch (error) {
        console.error(error);
    }
}

/**
 * Subscribe to a remote participant's remote video stream obj.
 * You have to subscribe to the 'isAvailableChanged' event to render the remoteVideoStream. If the 'isAvailable' property
 * changes to 'true', a remote participant is sending a stream. Whenever availability of a remote stream changes
 * you can choose to destroy the whole 'Renderer', a specific 'RendererView' or keep them, but this will result in displaying blank video frame.
 */
subscribeToRemoteVideoStream = async (remoteVideoStream) => {
    let renderer = new VideoStreamRenderer(remoteVideoStream);
    let view;
    let remoteVideoContainer = document.createElement('div');
    remoteVideoContainer.className = 'remote-video-container';

    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);
        }
    }
}

/**
 * Start your local video stream.
 * This will send your local video stream to remote participants so they can view it.
 */
startVideoButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        await call.startVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}

/**
 * Stop your local video stream.
 * This will stop your local video stream from being sent to remote participants.
 */
stopVideoButton.onclick = async () => {
    try {
        await call.stopVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}

/**
 * To render a LocalVideoStream, you need to create a new instance of VideoStreamRenderer, and then
 * create a new VideoStreamRendererView instance using the asynchronous createView() method.
 * You may then attach view.target to any UI element. 
 */
createLocalVideoStream = async () => {
    const camera = (await deviceManager.getCameras())[0];
    if (camera) {
        return new LocalVideoStream(camera);
    } else {
        console.error(`No camera device found on the system`);
    }
}

/**
 * Display your local video stream preview in your UI
 */
displayLocalVideoStream = async () => {
    try {
        localVideoStreamRenderer = new VideoStreamRenderer(localVideoStream);
        const view = await localVideoStreamRenderer.createView();
        localVideoContainer.hidden = false;
        localVideoContainer.appendChild(view.target);
    } catch (error) {
        console.error(error);
    } 
}

/**
 * Remove your local video stream preview from your UI
 */
removeLocalVideoStream = async() => {
    try {
        localVideoStreamRenderer.dispose();
        localVideoContainer.hidden = true;
    } catch (error) {
        console.error(error);
    } 
}

/**
 * End current room call
 */
hangUpCallButton.addEventListener("click", async () => {
    await call.hangUp();
});

Lägg till den lokala serverkoden för webpack

Skapa en fil i rotkatalogen i projektet med namnet webpack.config.js för att innehålla den lokala serverlogik som finns i den här snabbstarten. Lägg till följande kod i webpack.config.js:

const path = require('path');
const CopyPlugin = require("copy-webpack-plugin");

module.exports = {
    mode: 'development',
    entry: './index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
    },
    devServer: {
        static: {
            directory: path.join(__dirname, './')
        },
    },
    plugins: [
        new CopyPlugin({
            patterns: [
                './index.html'
            ]
        }),
    ]
};

Kör koden

Använd för webpack-dev-server att skapa och köra din app. Kör följande kommando för att paketa programvärden i en lokal webbserver:

`npx webpack serve --config webpack.config.js`
  1. Öppna webbläsaren navigera till http://localhost:8080/.
  2. I det första indatafältet anger du en giltig användaråtkomsttoken.
  3. Klicka på "Initiera samtalsagent" och ange ditt rums-ID.
  4. Klicka på "Koppla rumssamtal"

Nu har du anslutit till ett rumssamtal!

Förstå anslutning till ett rumssamtal

All kod som du har lagt till i snabbstartsappen gjorde att du kunde starta och ansluta till ett rumssamtal. Här är mer information om vilka fler metoder/hanterare du kan komma åt för Rum för att utöka funktionaliteten i ditt program.

Om du vill visa rollen för deltagarna i det lokala eller fjärranslutna samtalet prenumererar du på hanteraren nedan.

// Subscribe to changes for your role in a call
 const callRoleChangedHandler = () => {
 	console.log(call.role);
 };

 call.on('roleChanged', callRoleChangedHandler);

// Subscribe to role changes for remote participants
 const subscribeToRemoteParticipant = (remoteParticipant) => {
 	remoteParticipant.on('roleChanged', () => {
 	    console.log(remoteParticipant.role);
 	});
 }

Du kan lära dig mer om roller för rumssamtalsdeltagare i dokumentationen för rumskonceptet.

Ansluta till ett rumssamtal

Om du vill följa med i den här snabbstarten kan du ladda ned snabbstarten Room Call på GitHub.

Konfigurera

Skapa Xcode-projektet

I Xcode skapar du ett nytt iOS-projekt och väljer mallen Enkel vyapp. I den här självstudien används SwiftUI-ramverket, så du bör ange Språket till Swift och användargränssnittet till SwiftUI.

Skärmbild som visar fönstret Nytt projekt i Xcode.

Installera CocoaPods

Använd den här guiden för att installera CocoaPods på din Mac.

Installera paketet och beroenden med CocoaPods

  1. Om du vill skapa en Podfile för ditt program öppnar du terminalen och navigerar till projektmappen och kör podd-init.

  2. Lägg till följande kod i Podfile och spara:

platform :ios, '13.0'
use_frameworks!

target 'roomsquickstart' do
  pod 'AzureCommunicationCalling', '~> 2.5.0'
end
  1. Kör poddinstallation.

  2. .xcworkspace Öppna filen med Xcode.

Begär åtkomst till mikrofonen och kameran

För att få åtkomst till enhetens mikrofon och kamera måste du uppdatera appens informationsegenskapslista med NSMicrophoneUsageDescription och NSCameraUsageDescription. Ange det associerade värdet till en sträng som ska ingå i den dialogruta som systemet använder för att begära åtkomst från användaren.

Högerklicka på posten i Info.plist projektträdet och välj Öppna som > källkod. Lägg till följande rader i avsnittet på den översta nivån <dict> och spara sedan filen.

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

Konfigurera appramverket

Öppna projektets ContentView.swift fil och lägg till en importdeklaration överst i filen för att importera AzureCommunicationCalling biblioteket och AVFoundation. AVFoundation används för att samla in ljudbehörighet från kod.

import AzureCommunicationCalling
import AVFoundation

Objektmodell

Följande klasser och gränssnitt hanterar några av de viktigaste funktionerna i Azure Communication Services Calling SDK för iOS.

Name beskrivning
CallClient CallClient är den viktigaste startpunkten för anropande SDK.
CallAgent CallAgent används för att starta och hantera anrop.
CommunicationTokenCredential CommunicationTokenCredential används som tokenautentiseringsuppgifter för att instansiera CallAgent.
CommunicationIdentifier CommunicationIdentifier används för att representera användarens identitet och kan ha något av följande värden: CommunicationUserIdentifier/Telefon NumberIdentifier/CallingApplication.
RoomCallLocator RoomCallLocator används av CallAgent för att ansluta till ett rumssamtal

Skapa samtalsagenten

Ersätt implementeringen av ContentView-structen med några enkla användargränssnittskontroller som gör det möjligt för en användare att initiera och avsluta ett anrop. Vi kopplar affärslogik till dessa kontroller i den här snabbstarten.

struct ContentView: View {    
    @State var roomId: String = ""
    @State var callObserver:CallObserver?
    @State var previewRenderer: VideoStreamRenderer? = nil
    @State var previewView: RendererView? = nil
    @State var sendingLocalVideo: Bool = false
    @State var speakerEnabled: Bool = false
    @State var muted: Bool = false
    @State var callClient: CallClient?
    @State var call: Call?
    @State var callHandler: CallHandler?
    @State var callAgent: CallAgent?
    @State var deviceManager: DeviceManager?
    @State var localVideoStreams: [LocalVideoStream]?
    @State var callState: String = "Unknown"
    @State var showAlert: Bool = false
    @State var alertMessage: String = ""
    @State var participants: [[Participant]] = [[]]
    
    var body: some View {
        NavigationView {
            ZStack {
                if (call == nil) {
                    Form {
                        Section {
                            TextField("Room ID", text: $roomId)
                            Button(action: joinRoomCall) {
                                Text("Join Room Call")
                            }
                        }
                    }
                    .navigationBarTitle("Rooms Quickstart")
                } else {
                    ZStack {
                        VStack {
                            ForEach(participants, id:\.self) { array in
                                HStack {
                                    ForEach(array, id:\.self) { participant in
                                        ParticipantView(self, participant)
                                    }
                                }
                                .frame(maxWidth: .infinity, maxHeight: 200, alignment: .topLeading)
                            }
                        }
                        .background(Color.black)
                        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                        VStack {
                            if (sendingLocalVideo) {
                                HStack {
                                    RenderInboundVideoView(view: $previewView)
                                        .frame(width:90, height:160)
                                        .padding(10)
                                        .background(Color.green)
                                }
                                .frame(maxWidth: .infinity, alignment: .trailing)
                            }
                            HStack {
                                Button(action: toggleMute) {
                                    HStack {
                                        Text(muted ? "Unmute" : "Mute")
                                    }
                                    .frame(width:80)
                                    .padding(.vertical, 10)
                                    .background(Color(.lightGray))
                                }
                                Button(action: toggleLocalVideo) {
                                    HStack {
                                        Text(sendingLocalVideo ? "Video-Off" : "Video-On")
                                    }
                                    .frame(width:80)
                                    .padding(.vertical, 10)
                                    .background(Color(.lightGray))
                                }
                            }
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .padding(.horizontal, 10)
                            .padding(.vertical, 5)
                            HStack {
                                Button(action: leaveRoomCall) {
                                    HStack {
                                        Text("Leave Room Call")
                                    }
                                    .frame(width:80)
                                    .padding(.vertical, 10)
                                    .background(Color(.red))
                                }
                            }
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .padding(.horizontal, 10)
                            .padding(.vertical, 5)
                            HStack {
                                Text("Status:")
                                Text(callState)
                            }
                            .padding(.vertical, 10)
                        }
                        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
                    }
                }
            }
        }
        .onAppear{
            // Authenticate the client
            // Initialize the CallAgent and access Device Manager
            // Ask for permissions
        }
    }
}

//Functions and Observers

struct HomePageView_Previews: PreviewProvider {
    static var previews: some View {
        HomePageView()
    }
}

Autentisera klienten

För att initiera en CallAgent-instans behöver vi en användaråtkomsttoken som gör att vi kan ansluta till Rum-anrop.

När du har en token lägger du till följande kod i återanropet onAppear i ContentView.swift. Du måste ersätta <USER ACCESS TOKEN> med en giltig användaråtkomsttoken för resursen:

var userCredential: CommunicationTokenCredential?
do {
    userCredential = try CommunicationTokenCredential(token: "<USER ACCESS TOKEN>")
} catch {
    print("ERROR: It was not possible to create user credential.")
    return
}

Initiera CallAgent och få åtkomst till Enhetshanteraren

Om du vill skapa en CallAgent-instans från en CallClient använder du metoden callClient.createCallAgent som asynkront returnerar ett CallAgent-objekt när det har initierats. Med DeviceManager kan du räkna upp lokala enheter som kan användas i ett anrop för att överföra ljud-/videoströmmar. Du kan också begära behörighet från en användare för att få åtkomst till mikrofon/kamera.

self.callClient = CallClient()
self.callClient?.createCallAgent(userCredential: userCredential!) { (agent, error) in
    if error != nil {
        print("ERROR: It was not possible to create a call agent.")
        return
    } else {
        self.callAgent = agent
        print("Call agent successfully created.")
        self.callAgent!.delegate = callHandler
        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")
            }
        }
    }
}

Be om behörigheter

Vi måste lägga till följande kod i återanropet onAppear för att be om behörigheter för ljud och video.

AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
    if granted {
        AVCaptureDevice.requestAccess(for: .video) { (videoGranted) in
            /* NO OPERATION */
        }
    }
}

Ansluta till ett rumssamtal

Metoden joinRoomCall anges som den åtgärd som ska utföras när knappen Koppla rumssamtal knackas. I den här snabbstarten är samtal endast ljud som standard men kan ha video aktiverat när ett rum har anslutits.

func joinRoomCall() {
    if self.callAgent == nil {
        print("CallAgent not initialized")
        return
    }
    
    if (self.roomId.isEmpty) {
        print("Room ID not set")
        return
    }
    
    // Join a call with a Room ID
    let options = JoinCallOptions()
    let audioOptions = AudioOptions()
    audioOptions.muted = self.muted
    
    options.audioOptions = audioOptions
    
    let roomCallLocator = RoomCallLocator(roomId: roomId)
    self.callAgent!.join(with: roomCallLocator, joinCallOptions: options) { (call, error) in
        self.setCallAndObserver(call: call, error: error)
    }
}

CallObserver används för att hantera mellansamtalshändelser och fjärrdeltagare. Vi ställer in observatörerna setCallAndOberserver i funktionen.

func setCallAndObserver(call:Call!, error:Error?) {
    if (error == nil) {
        self.call = call
        self.callObserver = CallObserver(view:self)

        self.call!.delegate = self.callObserver

        if (self.call!.state == CallState.connected) {
            self.callObserver!.handleInitialCallState(call: call)
        }
    } else {
        print("Failed to get call object")
    }
}

Lämna ett rumssamtal

Metoden leaveRoomCall anges som den åtgärd som ska utföras när knappen Lämna rumssamtal knackas. Den hanterar att lämna ett anrop och rensar alla resurser som har skapats.

private func leaveRoomCall() {
    if (self.sendingLocalVideo) {
        self.call!.stopVideo(stream: self.localVideoStreams!.first!) { (error) in
            if (error != nil) {
                print("Failed to stop video")
            } else {
                self.sendingLocalVideo = false
                self.previewView = nil
                self.previewRenderer?.dispose()
                self.previewRenderer = nil
            }
        }
    }
    self.call?.hangUp(options: nil) { (error) in }
    self.participants.removeAll()
    self.call?.delegate = nil
    self.call = nil
}

Sändningsvideo

Under ett rumssamtal kan vi använda startVideo eller stopVideo starta eller sluta skicka LocalVideoStream till fjärrdeltagare.

func toggleLocalVideo() {
    if (self.sendingLocalVideo) {
        self.call!.stopVideo(stream: self.localVideoStreams!.first!) { (error) in
            if (error != nil) {
                print("Cannot stop video")
            } else {
                self.sendingLocalVideo = false
                self.previewView = nil
                self.previewRenderer!.dispose()
                self.previewRenderer = nil
            }
        }
    } else {
        let availableCameras = self.deviceManager!.cameras
        let scalingMode:ScalingMode = .crop
        if (self.localVideoStreams == nil) {
            self.localVideoStreams = [LocalVideoStream]()
        }
        self.localVideoStreams!.append(LocalVideoStream(camera: availableCameras.first!))
        self.previewRenderer = try! VideoStreamRenderer(localVideoStream: self.localVideoStreams!.first!)
        self.previewView = try! previewRenderer!.createView(withOptions: CreateViewOptions(scalingMode:scalingMode))
        self.call!.startVideo(stream: self.localVideoStreams!.first!) { (error) in
            if (error != nil) {
                print("Cannot start video")
            }
            else {
                self.sendingLocalVideo = true
            }
        }
    }
}

Stänga av lokalt ljud

Under ett rumssamtal kan vi använda mute eller unMute för att stänga av eller slå på mikrofonen.

func toggleMute() {
    if (self.muted) {
        call!.unmuteOutgoingAudio(completionHandler: { (error) in
            if error == nil {
                self.muted = false
            }
        })
    } else {
        call!.muteOutgoingAudio(completionHandler: { (error) in
            if error == nil {
                self.muted = true
            }
        })
    }
}

Hantera samtalsuppdateringar

Implementera en CallHandler för att hantera uppdateringshändelser för att hantera samtalsuppdateringar. Placera följande implementering i CallHandler.swift.

final class CallHandler: NSObject, CallAgentDelegate {
    public var owner: ContentView?

    private static var instance: CallHandler?
    static func getOrCreateInstance() -> CallHandler {
        if let c = instance {
            return c
        }
        instance = CallHandler()
        return instance!
    }

    private override init() {}
    
    public func callAgent(_ callAgent: CallAgent, didUpdateCalls args: CallsUpdatedEventArgs) {
        if let removedCall = args.removedCalls.first {
            owner?.call = nil
        }
    }
}

Vi måste skapa en instans av CallHandler genom att lägga till följande kod i återanropet onAppear i ContentView.swift:

self.callHandler = CallHandler.getOrCreateInstance()
self.callHandler.owner = self

Ange ett ombud till CallAgent efter att CallAgent har skapats:

self.callAgent!.delegate = callHandler

Hantering av fjärrdeltagare

Alla fjärrdeltagare representeras av typen RemoteParticipant och är tillgängliga via remoteParticipants samlingen på en samtalsinstans. Vi kan implementera en Participant klass för att bland annat hantera uppdateringar på fjärrvideoströmmar för fjärranslutna deltagare.

class Participant: NSObject, RemoteParticipantDelegate, ObservableObject {
    private var videoStreamCount = 0
    private let innerParticipant:RemoteParticipant
    private let call:Call
    private var renderedRemoteVideoStream:RemoteVideoStream?
    
    @Published var state:ParticipantState = ParticipantState.disconnected
    @Published var isMuted:Bool = false
    @Published var isSpeaking:Bool = false
    @Published var hasVideo:Bool = false
    @Published var displayName:String = ""
    @Published var videoOn:Bool = true
    @Published var renderer:VideoStreamRenderer? = nil
    @Published var rendererView:RendererView? = nil
    @Published var scalingMode: ScalingMode = .fit

    init(_ call: Call, _ innerParticipant: RemoteParticipant) {
        self.call = call
        self.innerParticipant = innerParticipant
        self.displayName = innerParticipant.displayName

        super.init()

        self.innerParticipant.delegate = self

        self.state = innerParticipant.state
        self.isMuted = innerParticipant.isMuted
        self.isSpeaking = innerParticipant.isSpeaking
        self.hasVideo = innerParticipant.videoStreams.count > 0
        if(self.hasVideo) {
            handleInitialRemoteVideo()
        }
    }

    deinit {
        self.innerParticipant.delegate = nil
    }

    func getMri() -> String {
        Utilities.toMri(innerParticipant.identifier)
    }

    func set(scalingMode: ScalingMode) {
        if self.rendererView != nil {
            self.rendererView!.update(scalingMode: scalingMode)
        }
        self.scalingMode = scalingMode
    }
    
    func handleInitialRemoteVideo() {
        renderedRemoteVideoStream = innerParticipant.videoStreams[0]
        renderer = try! VideoStreamRenderer(remoteVideoStream: renderedRemoteVideoStream!)
        rendererView = try! renderer!.createView()
    }

    func toggleVideo() {
        if videoOn {
            rendererView = nil
            renderer?.dispose()
            videoOn = false
        }
        else {
            renderer = try! VideoStreamRenderer(remoteVideoStream: innerParticipant.videoStreams[0])
            rendererView = try! renderer!.createView()
            videoOn = true
        }
    }

    func remoteParticipant(_ remoteParticipant: RemoteParticipant, didUpdateVideoStreams args: RemoteVideoStreamsEventArgs) {
        let hadVideo = hasVideo
        hasVideo = innerParticipant.videoStreams.count > 0
        if videoOn {
            if hadVideo && !hasVideo {
                // Remote user stopped sharing
                rendererView = nil
                renderer?.dispose()
            } else if hasVideo && !hadVideo {
                // remote user started sharing
                renderedRemoteVideoStream = innerParticipant.videoStreams[0]
                renderer = try! VideoStreamRenderer(remoteVideoStream: renderedRemoteVideoStream!)
                rendererView = try! renderer!.createView()
            } else if hadVideo && hasVideo {
                if args.addedRemoteVideoStreams.count > 0 {
                    if renderedRemoteVideoStream?.id == args.addedRemoteVideoStreams[0].id {
                        return
                    }
    
                    // remote user added a second video, so switch to the latest one
                    guard let rendererTemp = renderer else {
                        return
                    }
                    rendererTemp.dispose()
                    renderedRemoteVideoStream = args.addedRemoteVideoStreams[0]
                    renderer = try! VideoStreamRenderer(remoteVideoStream: renderedRemoteVideoStream!)
                    rendererView = try! renderer!.createView()
                } else if args.removedRemoteVideoStreams.count > 0 {
                    if args.removedRemoteVideoStreams[0].id == renderedRemoteVideoStream!.id {
                        // remote user stopped sharing video that we were rendering but is sharing
                        // another video that we can render
                        renderer!.dispose()

                        renderedRemoteVideoStream = innerParticipant.videoStreams[0]
                        renderer = try! VideoStreamRenderer(remoteVideoStream: renderedRemoteVideoStream!)
                        rendererView = try! renderer!.createView()
                    }
                }
            }
        }
    }

    func remoteParticipant(_ remoteParticipant: RemoteParticipant, didChangeDisplayName args: PropertyChangedEventArgs) {
        self.displayName = innerParticipant.displayName
    }
}

class Utilities {
    @available(*, unavailable) private init() {}

    public static func toMri(_ id: CommunicationIdentifier?) -> String {

        if id is CommunicationUserIdentifier {
            let communicationUserIdentifier = id as! CommunicationUserIdentifier
            return communicationUserIdentifier.identifier
        } else {
            return "<nil>"
        }
    }
}

Videoströmmar för fjärrdeltagare

Vi kan skapa en ParticipantView för att hantera rendering av videoströmmar av fjärranslutna deltagare. Placera implementeringen i ParticipantView.swift

struct ParticipantView : View, Hashable {
    static func == (lhs: ParticipantView, rhs: ParticipantView) -> Bool {
        return lhs.participant.getMri() == rhs.participant.getMri()
    }

    private let owner: HomePageView

    @State var showPopUp: Bool = false
    @State var videoHeight = CGFloat(200)
    @ObservedObject private var participant:Participant

    var body: some View {
        ZStack {
            if (participant.rendererView != nil) {
                HStack {
                    RenderInboundVideoView(view: $participant.rendererView)
                }
                .background(Color(.black))
                .frame(height: videoHeight)
                .animation(Animation.default)
            } else {
                HStack {
                    Text("No incoming video")
                }
                .background(Color(.red))
                .frame(height: videoHeight)
            }
        }
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(participant.getMri())
    }

    init(_ owner: HomePageView, _ participant: Participant) {
        self.owner = owner
        self.participant = participant
    }

    func resizeVideo() {
        videoHeight = videoHeight == 200 ? 150 : 200
    }

    func showAlert(_ title: String, _ message: String) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.owner.alertMessage = message
            self.owner.showAlert = true
        }
    }
}

struct RenderInboundVideoView: UIViewRepresentable {
    @Binding var view:RendererView!

    func makeUIView(context: Context) -> UIView {
        return UIView()
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        for view in uiView.subviews {
            view.removeFromSuperview()
        }
        if (view != nil) {
            uiView.addSubview(view)
        }
    }
}

Prenumerera på händelser

Vi kan implementera en CallObserver klass för att prenumerera på en samling händelser som ska meddelas när värden, till exempel remoteParticipants, ändras under anropet.

public class CallObserver : NSObject, CallDelegate
{
    private var owner: ContentView
    private var firstTimeCallConnected: Bool = true
    
    init(view: ContentView) {
        owner = view
        super.init()
    }

    public func call(_ call: Call, didChangeState args: PropertyChangedEventArgs) {
        let state = CallObserver.callStateToString(state:call.state)
        owner.callState = state
        if (call.state == CallState.disconnected) {
            owner.leaveRoomCall()
        }
        else if (call.state == CallState.connected) {
            if(self.firstTimeCallConnected) {
                self.handleInitialCallState(call: call);
            }
            self.firstTimeCallConnected = false;
        }
    }

    public func handleInitialCallState(call: Call) {
        // We want to build a matrix with max 2 columns

        owner.callState = CallObserver.callStateToString(state:call.state)
        var participants = [Participant]()

        // Add older/existing participants
        owner.participants.forEach { (existingParticipants: [Participant]) in
            participants.append(contentsOf: existingParticipants)
        }
        owner.participants.removeAll()

        // Add new participants to the collection
        for remoteParticipant in call.remoteParticipants {
            let mri = Utilities.toMri(remoteParticipant.identifier)
            let found = participants.contains { (participant) -> Bool in
                participant.getMri() == mri
            }

            if !found {
                let participant = Participant(call, remoteParticipant)
                participants.append(participant)
            }
        }

        // Convert 1-D array into a 2-D array with 2 columns
        var indexOfParticipant = 0
        while indexOfParticipant < participants.count {
            var newParticipants = [Participant]()
            newParticipants.append(participants[indexOfParticipant])
            indexOfParticipant += 1
            if (indexOfParticipant < participants.count) {
                newParticipants.append(participants[indexOfParticipant])
                indexOfParticipant += 1
            }
            owner.participants.append(newParticipants)
        }
    }

    public func call(_ call: Call, didUpdateRemoteParticipant args: ParticipantsUpdatedEventArgs) {
        var participants = [Participant]()
        // Add older/existing participants
        owner.participants.forEach { (existingParticipants: [Participant]) in
            participants.append(contentsOf: existingParticipants)
        }
        owner.participants.removeAll()

        // Remove deleted participants from the collection
        args.removedParticipants.forEach { p in
            let mri = Utilities.toMri(p.identifier)
            participants.removeAll { (participant) -> Bool in
                participant.getMri() == mri
            }
        }

        // Add new participants to the collection
        for remoteParticipant in args.addedParticipants {
            let mri = Utilities.toMri(remoteParticipant.identifier)
            let found = participants.contains { (view) -> Bool in
                view.getMri() == mri
            }

            if !found {
                let participant = Participant(call, remoteParticipant)
                participants.append(participant)
            }
        }

        // Convert 1-D array into a 2-D array with 2 columns
        var indexOfParticipant = 0
        while indexOfParticipant < participants.count {
            var array = [Participant]()
            array.append(participants[indexOfParticipant])
            indexOfParticipant += 1
            if (indexOfParticipant < participants.count) {
                array.append(participants[indexOfParticipant])
                indexOfParticipant += 1
            }
            owner.participants.append(array)
        }
    }

    private static func callStateToString(state:CallState) -> String {
        switch state {
        case .connected: return "Connected"
        case .connecting: return "Connecting"
        case .disconnected: return "Disconnected"
        case .disconnecting: return "Disconnecting"
        case .none: return "None"
        default: return "Unknown"
        }
    }
}

Kör koden

Du kan skapa och köra appen i iOS-simulatorn genom att välja Produktkörning > eller med hjälp av kortkommandot (⌘-R).

Möjligheten att ansluta till ett rumssamtal och visa rollerna för samtalsdeltagare är tillgänglig i iOS Mobile Calling SDK version 2.5.0 och senare.

Du kan lära dig mer om roller för rumssamtalsdeltagare i dokumentationen för rumskonceptet.

Exempelapp

Om du vill följa med i den här snabbstarten kan du ladda ned snabbstarten Room Call på GitHub.

Konfigurera projekt

Skapa en Android-app med en tom aktivitet

Skapa ett nytt projekt från Android Studio:

Skärmbild som visar hur du börjar skapa ett nytt Android Studio-projekt

Ge projektet namnet Room Call Snabbstart och välj Kotlin.

Skärmbild som visar nya projektegenskaper på skärmen För projektkonfiguration.

Installera -paketet

Lägg till följande rad i avsnittet på dependencies modulnivånbuild.gradle.

dependencies {
    ...
    //Ability to join a Rooms calls is available in 2.4.0 or above.
    implementation 'com.azure.android:azure-communication-calling:2.4.0'
    ...
}

Lägga till behörigheter i programmanifestet

Om du vill begära behörigheter som krävs för att göra ett anrop måste du först deklarera behörigheterna i programmanifestet (app/src/main/AndroidManifest.xml). Kopiera följande till manifestfilen:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-feature
        android:name="android.hardware.camera"
        android:required="false" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.CAMERA" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppTheme">

        <!--Our Calling SDK depends on the Apache HTTP SDK.
    When targeting Android SDK 28+, this library needs to be explicitly referenced.
    See https://developer.android.com/about/versions/pie/android-9.0-changes-28#apache-p-->
        <uses-library android:name="org.apache.http.legacy" android:required="false"/>
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Konfigurera layouten för appen

Du behöver en textinmatning för rums-ID: t, en knapp för att ringa samtalet och extra knapp för att hänga upp samtalet.

Gå till app/src/main/res/layout/activity_main.xmloch ersätt innehållet i filen med följande kod:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text_role"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Role:"
        android:textSize="16sp"
        android:textStyle="bold"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="16dp" />

    <TextView
        android:id="@+id/text_call_status"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Call Status"
        android:textSize="16sp"
        android:textStyle="bold"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="48dp" />

    <EditText
        android:id="@+id/room_id"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="Room ID"
        android:inputType="textPersonName"
        android:layout_marginTop="100dp"
        android:layout_marginHorizontal="20dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="260dp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <Button
            android:id="@+id/call_button"
            android:layout_width="wrap_content"
            android:layout_marginEnd="32dp"
            android:layout_height="wrap_content"
            android:text="Start Call" />

        <Button
            android:id="@+id/hangup_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hangup" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Skapa huvudaktiviteten

När layouten har skapats kan du lägga till logiken för att starta ett rumsanrop. Aktiviteten hanterar begäran om körningsbehörigheter, skapar samtalsagenten och anropar när knappen trycks ned.

Metoden onCreate anropar getAllPermissions och createAgent, och lägger till bindningarna för anropsknappen.

Den här händelsen inträffar bara en gång när aktiviteten skapas. Mer information om onCreatefinns i guiden Förstå aktivitetslivscykeln.

Gå till filen MainActivity.kt och ersätt innehållet med följande kod:

package com.contoso.roomscallquickstart

import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.media.AudioManager
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.azure.android.communication.calling.Call
import com.azure.android.communication.calling.CallAgent
import com.azure.android.communication.calling.CallClient
import com.azure.android.communication.calling.HangUpOptions
import com.azure.android.communication.calling.JoinCallOptions
import com.azure.android.communication.calling.RoomCallLocator
import com.azure.android.communication.common.CommunicationTokenCredential
import java.util.concurrent.ExecutionException

class MainActivity : AppCompatActivity() {
    private val allPermissions = arrayOf(
        Manifest.permission.RECORD_AUDIO,
        Manifest.permission.CAMERA,
        Manifest.permission.READ_PHONE_STATE
    )

    private val userToken = "<ACS_USER_TOKEN>"
    private lateinit var callAgent: CallAgent
    private var call: Call? = null

    private lateinit var roleTextView: TextView
    private lateinit var statusView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        getAllPermissions()
        createCallAgent()

        val callButton: Button = findViewById(R.id.call_button)
        callButton.setOnClickListener { startCall() }

        val hangupButton: Button = findViewById(R.id.hangup_button)
        hangupButton.setOnClickListener { endCall() }

        roleTextView = findViewById(R.id.text_role)
        statusView = findViewById(R.id.text_call_status)

        volumeControlStream = AudioManager.STREAM_VOICE_CALL
    }

    /**
     * Start a call
     */
    private fun startCall() {
        if (userToken.startsWith("<")) {
            Toast.makeText(this, "Please enter token in source code", Toast.LENGTH_SHORT).show()
            return
        }

        val roomIdView: EditText = findViewById(R.id.room_id)
        val roomId = roomIdView.text.toString()
        if (roomId.isEmpty()) {
            Toast.makeText(this, "Please enter room ID", Toast.LENGTH_SHORT).show()
            return
        }

        val joinCallOptions = JoinCallOptions()

        val roomCallLocator = RoomCallLocator(roomId)
        call = callAgent.join(applicationContext, roomCallLocator, joinCallOptions)
        
        call?.addOnStateChangedListener { setCallStatus(call?.state.toString()) }

        call?.addOnRoleChangedListener { setRoleText(call?.callParticipantRole.toString()) }
    }

    /**
     * Ends the call previously started
     */
    private fun endCall() {
        try {
            call?.hangUp(HangUpOptions())?.get()
        } catch (e: ExecutionException) {
            Toast.makeText(this, "Unable to hang up call", Toast.LENGTH_SHORT).show()
        }
    }

    /**
     * Create the call callAgent
     */
    private fun createCallAgent() {
            try {
                val credential = CommunicationTokenCredential(userToken)
                callAgent = CallClient().createCallAgent(applicationContext, credential).get()
            } catch (ex: Exception) {
                Toast.makeText(
                    applicationContext,
                    "Failed to create call callAgent.",
                    Toast.LENGTH_SHORT
                ).show()
            }
    }

    /**
     * Request each required permission if the app doesn't already have it.
     */
    private fun getAllPermissions() {
        val permissionsToAskFor = mutableListOf<String>()
        for (permission in allPermissions) {
            if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                permissionsToAskFor.add(permission)
            }
        }
        if (permissionsToAskFor.isNotEmpty()) {
            ActivityCompat.requestPermissions(this, permissionsToAskFor.toTypedArray(), 1)
        }
    }

    /**
     * Ensure all permissions were granted, otherwise inform the user permissions are missing.
     */
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        var allPermissionsGranted = true
        for (result in grantResults) {
            allPermissionsGranted = allPermissionsGranted && (result == PackageManager.PERMISSION_GRANTED)
        }
        if (!allPermissionsGranted) {
            Toast.makeText(this, "All permissions are needed to make the call.", Toast.LENGTH_LONG).show()
            finish()
        }
    }

    @SuppressLint("SetTextI18n")
    private fun setCallStatus(status: String?) {
        runOnUiThread {
            statusView.text = "Call Status: $status"
        }
    }
    @SuppressLint("SetTextI18n")
    private fun setRoleText(role: String?) {
        runOnUiThread {
            roleTextView.text = "Role: $role"
        }
    }
}

Kommentar

När du utformar din app bör du tänka på när dessa behörigheter ska begäras. Behörigheter bör begäras när de behövs, inte i förväg. Mer information finns i Android-behörighetsguiden.

Köra projektet

Innan du kör projektet ersätter du <ACS_USER_TOKEN> med MainActivity.kt din Användaråtkomsttoken för Azure Communication Services.

private val userToken = "<ACS_USER_TOKEN>"

Kör projektet på en emulator eller en fysisk enhet.

Du bör se ett fält för att ange ditt rums-ID och en knapp för att starta rumssamtalet. Ange ditt rums-ID och kontrollera att samtalsstatusen har ändrats tillsammans med din roll.

Förstå anslutning till ett rumssamtal

All kod som du har lagt till i snabbstartsappen gjorde att du kunde starta och ansluta till ett rumssamtal. Vi måste fördjupa oss i hur allt fungerar och vilka fler metoder/hanterare du kan komma åt för Rum.

Rumsanrop kopplas genom CallAgent vilket skapas med en giltig användartoken:

private fun createCallAgent() {
    try {
        val credential = CommunicationTokenCredential(userToken)
        callAgent = CallClient().createCallAgent(applicationContext, credential).get()
    } catch (ex: Exception) {
        Toast.makeText(
            applicationContext,
            "Failed to create call callAgent.",
            Toast.LENGTH_SHORT
        ).show()
    }
}

Med hjälp av CallAgent och RoomCallLocatorkan vi ansluta till ett rumsanrop med hjälp av metoden CallAgent.join som returnerar ett Call objekt:

 val joinCallOptions = JoinCallOptions()
 val roomCallLocator = RoomCallLocator(roomId)
 call = callAgent.join(applicationContext, roomCallLocator, joinCallOptions)
        

Ytterligare anpassning utöver MainActivity.ktfilen omfattar att Call prenumerera på händelser för att hämta uppdateringar:

call.addOnRemoteParticipantsUpdatedListener { args: ParticipantsUpdatedEvent? ->
    handleRemoteParticipantsUpdate(
        args!!
    )
}

call.addOnStateChangedListener { args: PropertyChangedEvent? ->
    this.handleCallOnStateChanged(
        args!!
    )
}

Du kan utöka MainActivity.kt ytterligare för att visa rollen för lokala eller fjärranslutna samtalsdeltagare med hjälp av dessa metoder och hanterare nedan.

// Get your role in the call
call.getCallParticipantRole();

// Subscribe to changes for your role in a call
private void isCallRoleChanged(PropertyChangedEvent propertyChangedEvent) {
    // handle self-role change
}

call.addOnRoleChangedListener(isCallRoleChanged);

// Subscribe to role changes for remote participants
private void isRoleChanged(PropertyChangedEvent propertyChangedEvent) {
    // handle remote participant role change
}

remoteParticipant.addOnRoleChangedListener(isRoleChanged);

// Get role of the remote participant
remoteParticipant.getCallParticipantRole();

Möjligheten att ansluta till ett rumssamtal och visa rollerna för samtalsdeltagare finns i Android Mobile Calling SDK version 2.4.0 och senare.

Du kan lära dig mer om roller för rumssamtalsdeltagare i dokumentationen för rumskonceptet.

Ansluta till ett rumssamtal

Om du vill ansluta till ett rumssamtal konfigurerar du ditt Windows-program med hjälp av guiden Lägg till videosamtal i klientappen . Du kan också ladda ned snabbstarten för videosamtal på GitHub.

Skapa en callAgent med en giltig användartoken:


var creds = new CallTokenCredential("<user-token>");

CallAgentOptions callAgentOptions = new CallAgentOptions();
callAgentOptions.DisplayName = "<display-name>";
callAgent = await callClient.CreateCallAgentAsync(creds, callAgentOptions);

callAgent Använd och RoomCallLocator för att ansluta ett rumsanrop CallAgent.JoinAsync returnerar metoden ett CommunicationCall objekt:


RoomCallLocator roomCallLocator = new RoomCallLocator('<RoomId>');

CommunicationCall communicationCall = await callAgent.JoinAsync(roomCallLocator, joinCallOptions);

CommunicationCall Prenumerera på händelser för att hämta uppdateringar:

private async void CommunicationCall_OnStateChanged(object sender, PropertyChangedEventArgs args) {
	var call = sender as CommunicationCall;
	if (sender != null)
	{
		switch (call.State){
			// Handle changes in call state
		}
	}
}
		

Om du vill visa rollen som samtalsdeltagare prenumererar du på rolländringarna:

private void RemoteParticipant_OnRoleChanged(object sender, Azure.Communication.Calling.WindowsClient.PropertyChangedEventArgs args)
{
    _ = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        System.Diagnostics.Trace.WriteLine("Raising Role change, new Role: " + remoteParticipant_.Role);
        PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("RemoteParticipantRole"));
    });
}

Möjligheten att ansluta till ett rumssamtal och visa rollerna för samtalsdeltagare är tillgänglig i Windows NuGet Release version 1.1.0 och senare.

Du kan lära dig mer om roller för rumssamtalsdeltagare i dokumentationen för rumskonceptet.

Nästa steg

I det här avsnittet har du lärt dig att:

  • Lägga till videosamtal i ditt program
  • Skicka rumsidentifieraren till den anropande SDK:en
  • Ansluta till ett rumssamtal från ditt program

Du kanske också vill: