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;
}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
- Create the client using
StreamVideoClient.CreateDefaultClient(); - Connect a user to Stream server with
await _client.ConnectUserAsync(authCredentials)
Full example:
For testing purposes you can use the following authorization credentials:
Here are credentials to try out the app with:
| Property | Value |
|---|---|
| API Key | |
| Token | |
| User ID | |
| Call ID |
For testing you can join the call on our web-app: Join Call
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";
// Passing create: true will create the call if it doesn't 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";
// Passing create: false means the call must already exist, otherwise 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
On Android and iOS, the user must grant permission to access 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
On Android and iOS, the user must grant permission to access 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
You must subscribe to participant tracks and bind them to Unity components in order to receive audio and video from other participants. Without calling SetAudioSourceTarget on each participant's StreamAudioTrack, you will not hear their audio. Without calling SetRenderTarget on their StreamVideoTrack, you will not see their video. This is the most common integration mistake.
Data streamed by participants is organized in form of "tracks". There are two type of tracks:
StreamAudioTrack- Audio Track, contains audio sent by a participantStreamVideoTrack- 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 turn on/off their camera or microphone. Subscribe to participant.TrackAdded to handle all tracks — this event is called for both existing and future tracks.
If a participant turns off their camera or microphone during the call, the track you've already received will be paused. Once re-enabled, the track resumes automatically. Note that TrackAdded may be called multiple times for the same participant — for example, if the network connection drops and the SDK reconnects under the hood, new track instances will be provided. Your handler should account for this by updating the existing bindings rather than assuming tracks are received only once.
By default, the SDK automatically subscribes to video tracks for up to 5 participants. Beyond this limit, incoming video is not requested automatically. You must manually enable video for additional participants using participant.SetIncomingVideoEnabled(true). Audio is always auto-subscribed with no limit. See Video Optimization for details.
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 participants
foreach (var participant in streamCall.Participants)
{
// Subscribe to TrackAdded event - it will be called for both existing and future tracks
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, subscribes to the TrackAdded event which handles both existing and future tracks.
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));
// Subscribe to TrackAdded event - it will be called for both existing and future tracks
_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.