Quickstart

This section will give you a quick overview on every integration step. Please refer to Core Concepts sections for more in-depth information and more code examples.

Client setup

  1. Create the client using StreamVideoClient.CreateDefaultClient();
  2. Connect a user to Stream server with await _client.ConnectUserAsync(authCredentials)

Full example:

using System;
using StreamVideo.Core;
using StreamVideo.Libs.Auth;
using UnityEngine;

public class VideoClient : MonoBehaviour
{
    async void Start()
    {
        _client = StreamVideoClient.CreateDefaultClient();

        try
        {
            var authCredentials = new AuthCredentials("api-key", "user-id", "user-token");
            await _client.ConnectUserAsync(authCredentials);
            
            // After the await ConnectUserAsync task completes, the client is connected
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
        }
    }

    private IStreamVideoClient _client;
}

For testing purposes you can use the following authorization credentials:

Read more on client authorization and obtaining auth tokens in the Client & Authentication section.

Creating and Joining Calls

Most commonly you will either want to join another call or create a new call and join it immediately.

You can use the _client.JoinCallAsync method for both scenarios. If you’re joining another call, set the create argument to false. If you’re creating a new call for others to join, set the create argument to true.

Create and Join the Call

var callType = StreamCallType.Default; // Call type affects default permissions
var callId = "my-call-id";

// Notice that we pass create argument as true - this will create the call if it doesn't already exist
var streamCall = await _client.JoinCallAsync(callType, callId, create: true, ring: true, notify: false);

Join another call

var callType = StreamCallType.Default; // Call type affects default permissions
var callId = "my-call-id";

// Notice that we pass create argument as false - if the call doesn't exist the join attempt will fail
var streamCall = await _client.JoinCallAsync(callType, callId, create: false, ring: true, notify: false);

Capture Audio from a Microphone

The AudioDeviceManager manages all interactions with camera devices. Below are several fundamental operations; for a comprehensive list, please visit our Camera & Microphone documentation section.

List available microphone devices:

var microphones = _client.AudioDeviceManager.EnumerateDevices();

foreach (var mic in microphones)
{
    Debug.Log(mic.Name);
}

Select active device:

var firstMicrophone = microphones.First();

// Select microphone device to capture audio input. `enable` argument determines whether audio capturing should start
_client.AudioDeviceManager.SelectDevice(firstMicrophone, enable: true);

The enable argument determines whether audio capture should start for this device.

You can start/stop audio capturing with:

// Start audio capturing
_client.AudioDeviceManager.Enable();

// Stop audio capturing
_client.AudioDeviceManager.Disable();

Android & iOS platforms

For platforms like Android and iOS, the user needs to grant permission to access the microphone devices. You can read more about requesting permissions in the Camera & Microphone docs section.

Capture Video from a Web Camera

The VideoDeviceManager manages all interactions with camera devices. Below are several fundamental operations; for a comprehensive list, please visit our Camera & Microphone documentation section.

List available camera devices:

var cameras = _client.VideoDeviceManager.EnumerateDevices();

foreach (var cam in cameras)
{
    Debug.Log(cam.Name);
}

Select active device:

var firstCamera = cameras.First();

// Select camera device to capture video input. `enable` argument determines whether video capturing should start
_client.VideoDeviceManager.SelectDevice(firstCamera, enable: true);

The enable argument determines whether video capture should start immediately for this device.

You can start/stop video capturing with:

// Start video capturing
_client.VideoDeviceManager.Enable();

// Stop video capturing
_client.VideoDeviceManager.Disable();

Android & iOS platforms

For platforms like Android and iOS, a user needs to grant permission to access the camera devices. You can read more about requesting permissions in the Camera & Microphone docs section.

Handling participants

Once you obtain a reference to a call object of type IStreamCall you can iterate through the current participants and subscribe to events to get notified whenever a participant joins or leaves.

var callType = StreamCallType.Default; // Call type affects default permissions
var callId = "my-call-id";

var streamCall = await _client.JoinCallAsync(callType, callId, create: false, ring: true, notify: false);

// Subscribe to events to get notified that streamCall.Participants collection changed
streamCall.ParticipantJoined += OnParticipantJoined;
streamCall.ParticipantLeft += OnParticipantLeft;

// Iterate through current participants, participant is an object of type IStreamVideoCallParticipant
foreach (var participant in streamCall.Participants)
{
    // Handle participant logic. For example: create a view for each participant
}

Handling participant tracks

Data streamed by participants is organized in form of “tracks”. There are two type of tracks:

  • StreamAudioTrack - Audio Track, contains audio sent by a participant
  • StreamVideoTrack - Video Track, contains video image sent by a participant

Each participant has its own tracks that can change during the call - for example a participant may decide to turn on/off camera or a microphone during the call. This is why you should not only process the tracks available in the participant.GetTracks() but also subscribe to participant.TrackAdded in order to get notified whenever a new track is added. If participant decides to turn off camera or microphone during the call the track that you’ve already received will be paused. Once the participant re-enables the camera or microphone the track will resume automatically. So you always need to process the track once.

Here’s an example of how you could process participant tracks:

var callType = StreamCallType.Default; // Call type affects default permissions
var callId = "my-call-id";

// JoinCall to get a IStreamCall object. You can also call _client.GetOrCreateCallAsync or _client.GetCallAsync
var streamCall = await _client.JoinCallAsync(callType, callId, create: false, ring: true, notify: false);

// Subscribe for participants change
streamCall.ParticipantJoined += OnParticipantJoined;
streamCall.ParticipantLeft += OnParticipantLeft;

// Process current participant
foreach (var participant in streamCall.Participants)
{
    // Handle currently available tracks
    foreach (var track in participant.GetTracks())
    {
        OnParticipantTrackAdded(participant, track);
    }

    // Subscribe to event in case new tracks are added
    participant.TrackAdded += OnParticipantTrackAdded;
}

And here’s the corresponding OnParticipantTrackAdded method:

private void OnParticipantTrackAdded(IStreamVideoCallParticipant participant, IStreamTrack track)
{
    switch (track)
    {
        case StreamAudioTrack streamAudioTrack:

            // This assumes that this gameObject contains the AudioSource component but it's not a requirement. You can obtain the AudioSource reference in your preferred way
            var audioSource = GetComponent<AudioSource>();
                
            // This AudioSource will receive audio from the participant
            streamAudioTrack.SetAudioSourceTarget(audioSource);
            break;

        case StreamVideoTrack streamVideoTrack:
            
            // This assumes that this gameObject contains the RawImage component but it's not a requirement. You can obtain the RawImage reference in your preferred way
            var rawImage = GetComponent<RawImage>();
                
            // This RawImage will receive video from the participant
            streamVideoTrack.SetRenderTarget(rawImage);
            break;
    }
}

Complete example of handling participants and their tracks

Video Manager

An example of script that creates an instance of IStreamVideoClient, connects a user to the Stream server and exposes a JoinCallAsync method that allows to create and join calls.

Once the call is joined, this script will create a view object for every participant and subscribe to ParticipantJoined and the ParticipantLeft events in order to create or destroy participant view objects.

public class VideoManager : MonoBehaviour
{
    public async Task JoinCallAsync(string callId, StreamCallType callType, bool create, bool ring, bool notify)
    {
        var streamCall = await _client.JoinCallAsync(callType, callId, create, ring, notify);
        
        // Subscribe to events to get notified that streamCall.Participants collection changed
        streamCall.ParticipantJoined += OnParticipantJoined;
        streamCall.ParticipantLeft += OnParticipantLeft;
        
        // Iterate through current participants
        foreach (var participant in streamCall.Participants)
        {
            // Handle participant logic. For example: create a view for each participant
            CreateParticipantView(participant);
        }
    }

    private void OnParticipantLeft(string sessionid, string userid)
    {
        // Try find view for this participant and destroy it because he left the call
        var viewInstance = _participantViews.FirstOrDefault(v => v.SessionId == sessionid);
        if (viewInstance != null)
        {
            // If the participant view was found -> destroy it
            Destroy(viewInstance.gameObject);
        }
    }

    private void OnParticipantJoined(IStreamVideoCallParticipant participant)
    {
        // Create view whenever new participant joins during the call
        CreateParticipantView(participant);
    }

    private void CreateParticipantView(IStreamVideoCallParticipant participant)
    {
        // Create new prefab instance for the view. In this example we'll add it as a child of this gameObject
        var viewInstance = Instantiate(_participantViewPrefab, transform);
        
        // Add to list so we can easily destroy it when a participant leaves the call
        _participantViews.Add(viewInstance);
        
        // Call ParticipantView.Init in order to process the participant tracks and subscribe to events
        viewInstance.Init(participant);
    }

    // Start() is called automatically by UnityEngine
    protected async void Start()
    {
        _client = StreamVideoClient.CreateDefaultClient();

        try
        {
            var authCredentials = new AuthCredentials("api-key", "user-id", "user-token");
            await _client.ConnectUserAsync(authCredentials);
        
            // After we awaited the ConnectUserAsync the client is connected
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
        }
    }
    
    [SerializeField]
    private ParticipantView _participantViewPrefab;
    
    private IStreamVideoClient _client;
    private readonly List<ParticipantView> _participantViews = new List<ParticipantView>();
}

Participant View

An example of script that you’d attach to a prefab gameObject that you’d spawn and initialize by calling the Init method per each participant in the call.

This script, once initialized with a participant object will handle currently available tracks and subscribe to TrackAdded event in order to handle tracks added later during the call. The OnParticipantTrackAdded method handles both audio and video tracks and binds them to an AudioSource and the RawImage respectively in order to handle received data.

    public class ParticipantView : MonoBehaviour
    {
        // Call this method to setup view for a participant
        public void Init(IStreamVideoCallParticipant participant)
        {
            if (_participant != null)
            {
                Debug.LogError("Participant view already initialized.");
                return;
            }

            _participant = participant ?? throw new ArgumentNullException(nameof(participant));

            // Handle currently available tracks
            foreach (var track in _participant.GetTracks())
            {
                OnParticipantTrackAdded(_participant, track);
            }

            // Subscribe to event in case new tracks are added
            _participant.TrackAdded += OnParticipantTrackAdded;

            _name.text = _participant.Name;
        }

        public void OnDestroy()
        {
            // It's a good practice to unsubscribe from events when the object is destroyed
            if (_participant != null)
            {
                _participant.TrackAdded -= OnParticipantTrackAdded;
            }
        }

        [SerializeField]
        private Text _name; // This will show participant name

        [SerializeField]
        private RawImage _video; // This will show participant video
        
        [SerializeField]
        private AudioSource _audioSource; // This will play participant audio

        private IStreamVideoCallParticipant _participant;

        private void OnParticipantTrackAdded(IStreamVideoCallParticipant participant, IStreamTrack track)
        {
            switch (track)
            {
                case StreamAudioTrack streamAudioTrack:

                    // Set AudioSource component to be the target of the audio track
                    streamAudioTrack.SetAudioSourceTarget(_audioSource);
                    break;

                case StreamVideoTrack streamVideoTrack:
                    
                    // Set RawImage component to be the target of the video track
                    streamVideoTrack.SetRenderTarget(_video);
                    break;
            }
        }
    }

You can study the Sample Project in order to get the full image of how you could approach the integration process.

© Getstream.io, Inc. All Rights Reserved.