-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathParticipantView.cs
More file actions
215 lines (174 loc) · 7.91 KB
/
Copy pathParticipantView.cs
File metadata and controls
215 lines (174 loc) · 7.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
using System;
using StreamVideo.Core;
using StreamVideo.Core.StatefulModels;
using StreamVideo.Core.StatefulModels.Tracks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace StreamVideo.ExampleProject.UI
{
public class ParticipantView : MonoBehaviour
{
public IStreamVideoCallParticipant Participant { get; private set; }
public void Init(IStreamVideoCallParticipant participant, StreamVideoManager videoManager)
{
_videoManager = videoManager ?? throw new ArgumentNullException(nameof(videoManager));
if (Participant != null)
{
throw new NotSupportedException("reusing participant view for new participant is not supported yet");
}
Participant = participant ?? throw new ArgumentNullException(nameof(participant));
foreach (var track in Participant.GetTracks())
{
OnParticipantTrackAdded(Participant, track);
}
Participant.TrackAdded += OnParticipantTrackAdded;
_name.text = Participant.Name;
}
public void UpdateIsDominantSpeaker(bool isDominantSpeaker)
{
var frameColor = isDominantSpeaker ? _dominantSpeakerFrameColor : _defaultSpeakerFrameColor;
_videoFrame.color = frameColor;
}
/// <summary>
/// Call this for local participant only. We will not receive the `Participant.TrackAdded` event for the local participant.
/// So in order to show the stream from a local camera we hook it up separately
/// </summary>
public void SetLocalCameraSource(WebCamTexture localWebCamTexture)
{
if (localWebCamTexture == null)
{
_video.texture = null;
return;
}
_video.texture = localWebCamTexture;
}
// Called by Unity Engine
protected void Awake()
{
_videoRectTransform = _video.GetComponent<RectTransform>();
_baseVideoRotation = _videoRectTransform.rotation;
_muteLocallyToggleButton.onClick.AddListener(OnMuteLocallyToggleClicked);
}
// Called by Unity Engine
protected void Update()
{
var rect = _videoRectTransform.rect;
var videoRenderedSize = new Vector2(rect.width, rect.height);
var forcedRequestedSize = new Vector2(_forceRequestedResolutionWidth, _forceRequestedResolutionHeight);
var finalRequestedSize = _forceRequestedResolution ? forcedRequestedSize : videoRenderedSize;
if (_lastRequestedResolution != finalRequestedSize)
{
_lastRequestedResolution = finalRequestedSize;
var videoResolution = new VideoResolution((int)finalRequestedSize.x, (int)finalRequestedSize.y);
// To optimize bandwidth we always request the video resolution that matches what we're actually rendering
Participant.UpdateRequestedVideoResolution(videoResolution);
Debug.Log($"Rendered resolution changed for participant `{Participant.UserId}`. Requested video resolution update to: {videoResolution}");
}
FixVideoOrientation();
}
/// <summary>
/// Mobile users can either stream in landscape mode or portrait mode. We must rotate the video texture to match the orientation of the device.
/// </summary>
private void FixVideoOrientation()
{
// For remote users we have their video track -> fix rotation based on the video track rotation angle
if (Participant != null && Participant.VideoTrack != null && Participant.VideoTrack is StreamVideoTrack streamVideoTrack)
{
_videoRectTransform.rotation = _baseVideoRotation * Quaternion.AngleAxis(-streamVideoTrack.VideoRotationAngle, Vector3.forward);
}
// For local user, we don't have a video track, so we get the video rotation angle directly from WebCamTexture
if (Participant != null && Participant.IsLocalParticipant && _video.texture is WebCamTexture sourceWebCamTexture)
{
_videoRectTransform.rotation = _baseVideoRotation * Quaternion.AngleAxis(-sourceWebCamTexture.videoRotationAngle, Vector3.forward);
}
}
// Called by Unity Engine
protected void OnDestroy()
{
if (Participant != null)
{
Participant.TrackAdded -= OnParticipantTrackAdded;
}
}
[SerializeField]
private TMP_Text _name;
[SerializeField]
private RawImage _video;
[SerializeField]
private RawImage _videoFrame;
[SerializeField]
private Color32 _dominantSpeakerFrameColor;
[SerializeField]
private Color32 _defaultSpeakerFrameColor;
[SerializeField]
private bool _forceRequestedResolution = false;
[SerializeField]
private int _forceRequestedResolutionWidth = 300;
[SerializeField]
private int _forceRequestedResolutionHeight = 300;
[SerializeField]
private GameObject _isMutedIcon;
[SerializeField]
private Button _muteLocallyToggleButton;
private AudioSource _audioSource;
private RectTransform _videoRectTransform;
private Vector2 _lastRequestedResolution;
private Quaternion _baseVideoRotation;
private StreamVideoManager _videoManager;
private void OnParticipantTrackAdded(IStreamVideoCallParticipant participant, IStreamTrack track)
{
Debug.Log($"Track received from `{participant.UserId}`, type: {track.GetType()}");
switch (track)
{
case StreamAudioTrack streamAudioTrack:
if (_audioSource != null)
{
//StreamTodo: debug why we're sometimes getting multiple audio tracks despite publishing a single audio track
//StreamTodo: handle multiple audio tracks. This is a valid use case
Debug.LogError("Multiple audio track!");
return;
}
_audioSource = gameObject.AddComponent<AudioSource>();
streamAudioTrack.SetAudioSourceTarget(_audioSource);
// Apply cached local mute state in case participant rejoined
if (_videoManager.IsParticipantMutedLocally(participant))
{
streamAudioTrack.MuteLocally();
UpdateMuteIcon();
}
break;
case StreamVideoTrack streamVideoTrack:
streamVideoTrack.SetRenderTarget(_video);
break;
default:
throw new ArgumentOutOfRangeException(nameof(track));
}
}
private void OnMuteLocallyToggleClicked()
{
if (Participant == null)
{
return;
}
var isMuted = _videoManager.IsParticipantMutedLocally(Participant);
var newIsMuted = !isMuted;
var actionLabel = newIsMuted ? "Muted" : "Unmuted";
Debug.Log(actionLabel + " participant with user ID: " + Participant.UserId);
if (newIsMuted)
{
_videoManager.MuteLocally(Participant);
}
else
{
_videoManager.UnmuteLocally(Participant);
}
UpdateMuteIcon();
}
private void UpdateMuteIcon()
{
var isMuted = _videoManager.IsParticipantMutedLocally(Participant);
_isMutedIcon.SetActive(isMuted);
}
}
}