Skip to main content

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:

Here are credentials to try out the app with:

PropertyValue
API KeyWaiting for an API key ...
Token Token is generated ...
User IDLoading ...
Call IDCreating random call ID ...

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

Setting Audio Input

Bind microphone device to a AudioSource component

This code will get the first microphone device from Unity's Microphone.devices list and stream it's input into a AudioSource component.

// Obtain reference to an AudioSource that will be used a source of audio
var inputAudioSource = GetComponent<AudioSource>();

// Get a valid microphone device name.
// You usually want to populate a dropdown list with Microphone.devices so that the user can pick which device should be used
_activeMicrophoneDeviceName = Microphone.devices.First();

inputAudioSource.clip
= Microphone.Start(_activeMicrophoneDeviceName, true, 3, AudioSettings.outputSampleRate);
inputAudioSource.loop = true;
inputAudioSource.Play();
  • For standalone platforms like Windows, macOS, and Linux, you usually want to provide the user with a dropdown menu populated from the Microphone.devices so that the user can select the active microphone.
  • For mobile platforms like Android or IOS the underlying OS is controlling the devices and the Microphone.devices

Please also note that on mobile platforms you need to request appropriate User Permissions in order to make use of the Microphone and the Camera.

Set AudioSource component as an Input Source for Audio.

You can provide any AudioSource component as an input for audio. All you need to do is call the _client.SetAudioInputSource(audioSource) method as in the following example:

// Obtain reference to an AudioSource that will be used a source of audio
var audioSource = GetComponent<AudioSource>();
_client.SetAudioInputSource(audioSource);

Now the provided AudioSource will be used as an audio input for audio communication in calls.

Please note that the AudioSrouce does not necessarily need to be associated with a microphone device. This is indeed the most common use case but the AudioSource in fact serves as an audio buffer, so you can implement many other use cases with how the audio input is gathered.

Read more

Please refer to Unity's documentation for more information on how to use Microphone devices:

Setting Video Input

You can use Unity's WebCamTexture to interact with the camera device.

Once you select the camera device you should create a new instance of WebCamTexture and call Play() on it.

Now you can set the WebCamTexture as a video input with _client.SetCameraInputSource(activeCamera); as shown in the following example:

// Obtain a camera device
var cameraDevice = WebCamTexture.devices.First();

var width = 1920;
var height = 1080;
var fps = 30;

// Use device name to create a new WebCamTexture instance
var activeCamera = new WebCamTexture(cameraDevice.name, width, height, fps);

// Call Play() in order to start capturing the video
activeCamera.Play();

// Set WebCamTexture in Stream's Client - this WebCamTexture will be the video source in video calls
_client.SetCameraInputSource(activeCamera);
  • For standalone platforms like Windows, macOS, and Linux, you usually want to provide the user with a dropdown menu populated from the WebCamTexture.devices so that the user can select the active camera.
  • On mobile platforms like Android or IOS there will usually be two camera devices present in the WebCamTexture.devices: The front camera and the back camera. So you may either select the front camera automatically or give user an option to toggle between front and back cameras depending on your use case.

Read more

Please refer to Unity's documentation for more information on how to use Camera devices:

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.

Did you find this page helpful?