protected virtual void OnEnable() { if (Source != null) { return; } // Create the external source //< TODO - Better abstraction if (typeof(T) == typeof(I420AVideoFrameStorage)) { Source = ExternalVideoTrackSource.CreateFromI420ACallback(OnFrameRequested); } else if (typeof(T) == typeof(Argb32VideoFrameStorage)) { Source = ExternalVideoTrackSource.CreateFromArgb32Callback(OnFrameRequested); } else { throw new NotSupportedException("This frame storage is not supported. Use I420AVideoFrameStorage or Argb32VideoFrameStorage."); } if (Source == null) { throw new Exception("Failed to create external video track source."); } VideoStreamStarted.Invoke(Source); }
public async Task VideoCall() { // This test use manual offers suspendOffer1_ = true; // Create video transceiver on #1. This triggers a renegotiation needed event. string name1 = "video_feed"; var initSettings = new TransceiverInitSettings { Name = name1, InitialDesiredDirection = Transceiver.Direction.SendOnly, StreamIDs = new List <string> { "id1", "id2" } }; var transceiver1 = pc1_.AddTransceiver(MediaKind.Video, initSettings); var track_config = new LocalVideoTrackInitConfig { trackName = "custom_i420a" }; // Add a local video track. using (var source = ExternalVideoTrackSource.CreateFromI420ACallback( VideoTrackSourceTests.CustomI420AFrameCallback)) { using (var localTrack = LocalVideoTrack.CreateFromSource(source, track_config)) { transceiver1.LocalVideoTrack = localTrack; // Connect await DoNegotiationStartFrom(pc1_); // Find the remote track Assert.AreEqual(1, pc2_.Transceivers.Count); var transceiver2 = pc2_.Transceivers[0]; var remoteTrack = transceiver2.RemoteVideoTrack; Assert.IsNotNull(remoteTrack); Assert.AreEqual(transceiver2, remoteTrack.Transceiver); Assert.AreEqual(pc2_, remoteTrack.PeerConnection); // Remote track receives frames. VideoTrackSourceTests.TestFrameReadyCallbacks(remoteTrack); // Cleanup. transceiver1.LocalVideoTrack = null; } } }
public void FrameReadyCallbacks() { var track_config = new LocalVideoTrackInitConfig { trackName = "custom_i420a" }; using (var source = ExternalVideoTrackSource.CreateFromI420ACallback( VideoTrackSourceTests.CustomI420AFrameCallback)) { using (var track = LocalVideoTrack.CreateFromSource(source, track_config)) { VideoTrackSourceTests.TestFrameReadyCallbacks(track); } } }
protected override Task CreateLocalVideoTrackAsync() { // Ensure the track has a valid name string trackName = TrackName; if (string.IsNullOrEmpty(trackName)) { // Generate a unique name (GUID) trackName = Guid.NewGuid().ToString(); TrackName = trackName; } SdpTokenAttribute.Validate(trackName, allowEmpty: false); // Create the external source //< TODO - Better abstraction if (typeof(T) == typeof(I420AVideoFrameStorage)) { Source = ExternalVideoTrackSource.CreateFromI420ACallback(OnFrameRequested); } else if (typeof(T) == typeof(Argb32VideoFrameStorage)) { Source = ExternalVideoTrackSource.CreateFromArgb32Callback(OnFrameRequested); } else { throw new NotSupportedException("This frame storage is not supported. Use I420AVideoFrameStorage or Argb32VideoFrameStorage."); } if (Source == null) { throw new Exception("Failed to create external video track source."); } // Create the local video track Track = LocalVideoTrack.CreateFromExternalSource(trackName, Source); if (Track == null) { throw new Exception("Failed ot create webcam video track."); } // Synchronize the track status with the Unity component status Track.Enabled = enabled; // This implementation is fast, so executes synchronously. return(Task.CompletedTask); }
protected virtual void OnEnable() { Debug.Assert(Source == null); // Create the external source //< TODO - Better abstraction if (typeof(T) == typeof(I420AVideoFrameStorage)) { AttachSource(ExternalVideoTrackSource.CreateFromI420ACallback(OnFrameRequested)); } else if (typeof(T) == typeof(Argb32VideoFrameStorage)) { AttachSource(ExternalVideoTrackSource.CreateFromArgb32Callback(OnFrameRequested)); } else { throw new NotSupportedException("This frame storage is not supported. Use I420AVideoFrameStorage or Argb32VideoFrameStorage."); } }
/// <summary> /// Add a new track to the peer connection and start the video track playback. /// </summary> public void StartTrack() { // Ensure the track has a valid name string trackName = TrackName; if (trackName == null || trackName.Length == 0) { // Generate a unique name (GUID) trackName = Guid.NewGuid().ToString(); TrackName = trackName; } //SdpTokenAttribute.Validate(trackName, allowEmpty: false); // Create the external source var nativePeer = PeerConnection; //< TODO - Better abstraction if (typeof(T) == typeof(I420AVideoFrameStorage)) { Source = ExternalVideoTrackSource.CreateFromI420ACallback(OnFrameRequested); } else if (typeof(T) == typeof(Argb32VideoFrameStorage)) { Source = ExternalVideoTrackSource.CreateFromArgb32Callback(OnFrameRequested); } else { throw new NotSupportedException(""); } // Create the local video track if (Source != null) { Track = nativePeer.AddCustomLocalVideoTrack(trackName, Source); if (Track != null) { NotifyVideoStreamStarted(); } } }
public void MultiExternalI420A() { // Batch changes in this test, and manually (re)negotiate suspendOffer1_ = true; // Connect StartOfferWith(pc1_); WaitForTransportsWritable(); Assert.True(pc1_.IsConnected); Assert.True(pc2_.IsConnected); WaitForSdpExchangeCompleted(); // No track yet Assert.AreEqual(0, pc1_.LocalVideoTracks.Count()); Assert.AreEqual(0, pc1_.RemoteVideoTracks.Count()); Assert.AreEqual(0, pc2_.LocalVideoTracks.Count()); Assert.AreEqual(0, pc2_.RemoteVideoTracks.Count()); // Create external I420A source var source1 = ExternalVideoTrackSource.CreateFromI420ACallback( VideoTrackSourceTests.CustomI420AFrameCallback); Assert.NotNull(source1); Assert.AreEqual(0, source1.Tracks.Count()); // Add external I420A tracks const int kNumTracks = 5; var transceivers = new Transceiver[kNumTracks]; var tracks = new LocalVideoTrack[kNumTracks]; for (int i = 0; i < kNumTracks; ++i) { var transceiver_settings = new TransceiverInitSettings { Name = $"transceiver1_{i}", }; transceivers[i] = pc1_.AddTransceiver(MediaKind.Video, transceiver_settings); Assert.NotNull(transceivers[i]); var track_config = new LocalVideoTrackInitConfig { trackName = $"track_i420a_{i}" }; tracks[i] = LocalVideoTrack.CreateFromSource(source1, track_config); Assert.NotNull(tracks[i]); Assert.IsTrue(source1.Tracks.Contains(tracks[i])); transceivers[i].LocalVideoTrack = tracks[i]; Assert.AreEqual(pc1_, tracks[i].PeerConnection); Assert.IsTrue(pc1_.LocalVideoTracks.Contains(tracks[i])); } Assert.AreEqual(kNumTracks, source1.Tracks.Count()); Assert.AreEqual(kNumTracks, pc1_.LocalVideoTracks.Count()); Assert.AreEqual(0, pc1_.RemoteVideoTracks.Count()); // Wait for local SDP re-negotiation on #1 Assert.True(renegotiationEvent1_.Wait(TimeSpan.FromSeconds(60.0))); // Renegotiate StartOfferWith(pc1_); // Confirm remote track was added on #2 Assert.True(videoTrackAddedEvent2_.Wait(TimeSpan.FromSeconds(60.0))); // Wait until SDP renegotiation finished WaitForSdpExchangeCompleted(); Assert.AreEqual(0, pc2_.LocalVideoTracks.Count()); Assert.AreEqual(kNumTracks, pc2_.RemoteVideoTracks.Count()); // Remove the track from #1 renegotiationEvent1_.Reset(); for (int i = 0; i < kNumTracks; ++i) { transceivers[i].LocalVideoTrack = null; Assert.IsNull(tracks[i].PeerConnection); Assert.IsFalse(pc1_.LocalVideoTracks.Contains(tracks[i])); Assert.IsTrue(source1.Tracks.Contains(tracks[i])); // does not change yet tracks[i].Dispose(); tracks[i] = null; Assert.IsFalse(source1.Tracks.Contains(tracks[i])); } Assert.AreEqual(0, pc1_.LocalVideoTracks.Count()); Assert.AreEqual(0, pc1_.RemoteVideoTracks.Count()); Assert.AreEqual(0, source1.Tracks.Count()); // Dispose of source source1.Dispose(); source1 = null; // Changes on transceiver's local track do not require renegotiation Assert.False(renegotiationEvent1_.IsSet); // Change the transceivers direction to stop sending renegotiationEvent1_.Reset(); videoTrackRemovedEvent2_.Reset(); remoteDescAppliedEvent1_.Reset(); remoteDescAppliedEvent2_.Reset(); for (int i = 0; i < kNumTracks; ++i) { transceivers[i].DesiredDirection = Transceiver.Direction.Inactive; } // Renegotiate manually the batch of changes Assert.True(renegotiationEvent1_.Wait(TimeSpan.FromSeconds(60.0))); StartOfferWith(pc1_); // Wait everything to be ready WaitForSdpExchangeCompleted(); // Confirm remote tracks were removed from #2 as part of removing all transceivers Assert.True(videoTrackRemovedEvent2_.IsSet); // Remote peer #2 doesn't have any track anymore Assert.AreEqual(0, pc2_.LocalVideoTracks.Count()); Assert.AreEqual(0, pc2_.RemoteVideoTracks.Count()); }
public void SimpleExternalI420A() { // Connect StartOfferWith(pc1_); WaitForTransportsWritable(); Assert.True(pc1_.IsConnected); Assert.True(pc2_.IsConnected); WaitForSdpExchangeCompleted(); // No track yet Assert.AreEqual(0, pc1_.LocalVideoTracks.Count()); Assert.AreEqual(0, pc1_.RemoteVideoTracks.Count()); Assert.AreEqual(0, pc2_.LocalVideoTracks.Count()); Assert.AreEqual(0, pc2_.RemoteVideoTracks.Count()); // Create external I420A source var source1 = ExternalVideoTrackSource.CreateFromI420ACallback( VideoTrackSourceTests.CustomI420AFrameCallback); Assert.NotNull(source1); Assert.AreEqual(0, source1.Tracks.Count()); // Add video transceiver #1 renegotiationEvent1_.Reset(); remoteDescAppliedEvent1_.Reset(); remoteDescAppliedEvent2_.Reset(); Assert.IsFalse(videoTrackAddedEvent2_.IsSet); var transceiver_settings = new TransceiverInitSettings { Name = "transceiver1", }; var transceiver1 = pc1_.AddTransceiver(MediaKind.Video, transceiver_settings); Assert.NotNull(transceiver1); Assert.IsNull(transceiver1.LocalTrack); Assert.IsNull(transceiver1.RemoteTrack); Assert.AreEqual(pc1_, transceiver1.PeerConnection); // Wait for renegotiation Assert.True(renegotiationEvent1_.Wait(TimeSpan.FromSeconds(60.0))); Assert.True(videoTrackAddedEvent2_.Wait(TimeSpan.FromSeconds(60.0))); Assert.True(remoteDescAppliedEvent1_.Wait(TimeSpan.FromSeconds(60.0))); Assert.True(remoteDescAppliedEvent2_.Wait(TimeSpan.FromSeconds(60.0))); WaitForSdpExchangeCompleted(); // Create external I420A track var track_config1 = new LocalVideoTrackInitConfig { trackName = "custom_i420a" }; var track1 = LocalVideoTrack.CreateFromSource(source1, track_config1); Assert.NotNull(track1); Assert.AreEqual(source1, track1.Source); Assert.IsNull(track1.PeerConnection); Assert.IsNull(track1.Transceiver); Assert.IsFalse(pc1_.LocalVideoTracks.Contains(track1)); // Set track on transceiver renegotiationEvent1_.Reset(); transceiver1.LocalVideoTrack = track1; Assert.AreEqual(pc1_, track1.PeerConnection); Assert.IsTrue(pc1_.LocalVideoTracks.Contains(track1)); Assert.IsFalse(renegotiationEvent1_.IsSet); // renegotiation not needed // Remove the track from #1 renegotiationEvent1_.Reset(); transceiver1.LocalVideoTrack = null; Assert.IsNull(track1.PeerConnection); Assert.IsNull(track1.Transceiver); Assert.IsFalse(pc1_.LocalVideoTracks.Contains(track1)); // Dispose of the track and its source track1.Dispose(); track1 = null; source1.Dispose(); source1 = null; // On peer #1 the track was replaced on the transceiver, but the transceiver stays // on the peer connection, so no renegotiation is needed. Assert.IsFalse(renegotiationEvent1_.IsSet); }
public CallHandler(ICall statefulCall, PeerConnection peerConnection) : base(TimeSpan.FromMinutes(10), statefulCall?.GraphLogger) { this.Call = statefulCall; this.callHandlerVideo = new CallHandlerVideo(this.Call); this.callHandlerAudio = new CallHandlerAudio(this.Call); this.peerConnection = peerConnection; this.peerConnection.VideoTrackAdded += this.callHandlerVideo.OnClientVideoTrackAdded; this.peerConnection.VideoTrackRemoved += this.callHandlerVideo.OnClientVideoTrackRemoved; this.peerConnection.AudioTrackAdded += this.callHandlerAudio.OnClientAudioTrackAdded; this.peerConnection.AudioTrackRemoved += this.callHandlerAudio.OnClientAudioTrackRemoved; TransceiverInitSettings transceiverInitSettings = new TransceiverInitSettings(); transceiverInitSettings.InitialDesiredDirection = Transceiver.Direction.Inactive; if (this.peerConnection.AssociatedTransceivers.ToList().Count != 0) { this.teamsAudioTransceiver = this.peerConnection.AssociatedTransceivers.ToList()[0]; this.callHandlerAudio.clientAudioTrack = this.peerConnection.AssociatedTransceivers.ToList()[0].RemoteAudioTrack; this.callHandlerAudio.clientAudioTrack.AudioFrameReady += this.callHandlerAudio.OnClientAudioReceived; this.teamsVideoTransceiver = this.peerConnection.AssociatedTransceivers.ToList()[1]; this.callHandlerVideo.clientVideoTrack = this.peerConnection.AssociatedTransceivers.ToList()[1].RemoteVideoTrack; this.callHandlerVideo.clientVideoTrack.I420AVideoFrameReady += this.callHandlerVideo.OnClientVideoReceived; } else { this.teamsAudioTransceiver = this.peerConnection.AddTransceiver(MediaKind.Audio, transceiverInitSettings); this.teamsVideoTransceiver = this.peerConnection.AddTransceiver(MediaKind.Video, transceiverInitSettings); } LocalVideoTrack teamsVideoTrack = LocalVideoTrack.CreateFromExternalSource("TeamsVideoTrack", ExternalVideoTrackSource.CreateFromI420ACallback(this.callHandlerVideo.CustomI420AFrameCallback)); this.teamsVideoTransceiver.LocalVideoTrack = teamsVideoTrack; this.teamsVideoTransceiver.DesiredDirection = Transceiver.Direction.SendReceive; LocalAudioTrack teamsAudioTrack = LocalAudioTrack.CreateFromExternalSource("TeamsAudioTrack", ExternalAudioTrackSource.CreateFromCallback(this.callHandlerAudio.CustomAudioFrameCallback)); this.teamsAudioTransceiver.LocalAudioTrack = teamsAudioTrack; this.teamsAudioTransceiver.DesiredDirection = Transceiver.Direction.SendReceive; this.Call.OnUpdated += this.OnCallUpdated; if (this.Call.GetLocalMediaSession() != null) { this.Call.GetLocalMediaSession().AudioSocket.DominantSpeakerChanged += this.OnDominantSpeakerChanged; this.Call.GetLocalMediaSession().VideoSocket.VideoMediaReceived += this.callHandlerVideo.OnTeamsVideoReceived; this.Call.GetLocalMediaSession().AudioSocket.AudioMediaReceived += this.callHandlerAudio.OnTeamsAudioReceived; } this.Call.Participants.OnUpdated += this.OnParticipantsUpdated; this.endCallTimer = new Timer(CallHandler.WaitForMs); this.endCallTimer.Enabled = false; this.endCallTimer.AutoReset = false; this.endCallTimer.Elapsed += this.OnTimerElapsed; }