diff --git a/talk/app/webrtc/java/jni/peerconnection_jni.cc b/talk/app/webrtc/java/jni/peerconnection_jni.cc index 1e138f6b94..85e10a9150 100644 --- a/talk/app/webrtc/java/jni/peerconnection_jni.cc +++ b/talk/app/webrtc/java/jni/peerconnection_jni.cc @@ -374,6 +374,14 @@ class PCOJava : public PeerConnectionObserver { CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } + void OnFirstMediaPacketReceived() override { + ScopedLocalRefFrame local_ref_frame(jni()); + jmethodID m = GetMethodID(jni(), *j_observer_class_, + "onFirstMediaPacketReceived", "()V"); + jni()->CallVoidMethod(*j_observer_global_, m); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; + } + void SetConstraints(ConstraintsWrapper* constraints) { RTC_CHECK(!constraints_.get()) << "constraints already set!"; constraints_.reset(constraints); diff --git a/talk/app/webrtc/java/src/org/webrtc/PeerConnection.java b/talk/app/webrtc/java/src/org/webrtc/PeerConnection.java index 36cd07595c..331c06257a 100644 --- a/talk/app/webrtc/java/src/org/webrtc/PeerConnection.java +++ b/talk/app/webrtc/java/src/org/webrtc/PeerConnection.java @@ -86,6 +86,9 @@ public class PeerConnection { /** Triggered when renegotiation is necessary. */ public void onRenegotiationNeeded(); + + /** Called when the first RTP packet is received. */ + public void onFirstMediaPacketReceived(); } /** Java version of PeerConnectionInterface.IceServer. */ diff --git a/talk/app/webrtc/java/testcommon/src/org/webrtc/PeerConnectionTest.java b/talk/app/webrtc/java/testcommon/src/org/webrtc/PeerConnectionTest.java index 50f4d7317f..8db0ded9f7 100644 --- a/talk/app/webrtc/java/testcommon/src/org/webrtc/PeerConnectionTest.java +++ b/talk/app/webrtc/java/testcommon/src/org/webrtc/PeerConnectionTest.java @@ -289,6 +289,9 @@ public class PeerConnectionTest { ++expectedStatsCallbacks; } + @Override + public synchronized void onFirstMediaPacketReceived() {} + public synchronized LinkedList takeStatsReports() { LinkedList got = gotStatsReports; gotStatsReports = new LinkedList(); diff --git a/talk/app/webrtc/objc/RTCPeerConnectionObserver.h b/talk/app/webrtc/objc/RTCPeerConnectionObserver.h index 9b981b9307..f5fb8dc6bb 100644 --- a/talk/app/webrtc/objc/RTCPeerConnectionObserver.h +++ b/talk/app/webrtc/objc/RTCPeerConnectionObserver.h @@ -68,6 +68,9 @@ class RTCPeerConnectionObserver : public PeerConnectionObserver { // New Ice candidate have been found. void OnIceCandidate(const IceCandidateInterface* candidate) override; + // Called when the first RTP packet is received. + void OnFirstMediaPacketReceived() override; + private: __weak RTCPeerConnection* _peerConnection; }; diff --git a/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm b/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm index 411cd6cb89..47246cbef7 100644 --- a/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm +++ b/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm @@ -105,4 +105,9 @@ void RTCPeerConnectionObserver::OnIceCandidate( gotICECandidate:iceCandidate]; } +void RTCPeerConnectionObserver::OnFirstMediaPacketReceived() { + id delegate = _peerConnection.delegate; + [delegate peerConnectionOnFirstMediaPacketReceived:_peerConnection]; +} + } // namespace webrtc diff --git a/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h b/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h index bf0c23104a..63529cbea3 100644 --- a/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h +++ b/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h @@ -69,4 +69,8 @@ - (void)peerConnection:(RTCPeerConnection*)peerConnection didOpenDataChannel:(RTCDataChannel*)dataChannel; +// Called when the first RTP packet is received. +- (void)peerConnectionOnFirstMediaPacketReceived: + (RTCPeerConnection *)peerConnection; + @end diff --git a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m index 892c461980..3f237262e6 100644 --- a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m +++ b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m @@ -233,6 +233,10 @@ @"Unexpected state"); } +- (void)peerConnectionOnFirstMediaPacketReceived: + (RTCPeerConnection*)peerConnection { +} + #pragma mark - RTCDataChannelDelegate - (void)channelDidChangeState:(RTCDataChannel*)channel { diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc index beae770978..d2f8d77b65 100644 --- a/talk/app/webrtc/peerconnection.cc +++ b/talk/app/webrtc/peerconnection.cc @@ -663,6 +663,8 @@ bool PeerConnection::Initialize( this, &PeerConnection::OnDataChannelDestroyed); session_->SignalDataChannelOpenMessage.connect( this, &PeerConnection::OnDataChannelOpenMessage); + session_->SignalFirstMediaPacketReceived.connect( + this, &PeerConnection::OnFirstMediaPacketReceived); return true; } @@ -2029,6 +2031,11 @@ void PeerConnection::OnDataChannelOpenMessage( DataChannelProxy::Create(signaling_thread(), channel)); } +void PeerConnection::OnFirstMediaPacketReceived() { + RTC_DCHECK(signaling_thread()->IsCurrent()); + observer_->OnFirstMediaPacketReceived(); +} + RtpSenderInterface* PeerConnection::FindSenderById(const std::string& id) { auto it = std::find_if(senders_.begin(), senders_.end(), diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h index 03b5853c29..83c408ba5b 100644 --- a/talk/app/webrtc/peerconnection.h +++ b/talk/app/webrtc/peerconnection.h @@ -326,6 +326,7 @@ class PeerConnection : public PeerConnectionInterface, // webrtc::DataChannel should be opened. void OnDataChannelOpenMessage(const std::string& label, const InternalDataChannelInit& config); + void OnFirstMediaPacketReceived(); RtpSenderInterface* FindSenderById(const std::string& id); diff --git a/talk/app/webrtc/peerconnection_unittest.cc b/talk/app/webrtc/peerconnection_unittest.cc index cf53c44f11..9e5ca2fe00 100644 --- a/talk/app/webrtc/peerconnection_unittest.cc +++ b/talk/app/webrtc/peerconnection_unittest.cc @@ -251,6 +251,7 @@ class PeerConnectionTestClient : public webrtc::PeerConnectionObserver, signaling_message_receiver_->ReceiveIceMessage( candidate->sdp_mid(), candidate->sdp_mline_index(), ice_sdp); } + void OnFirstMediaPacketReceived() override { received_media_packet_ = true; } // MediaStreamInterface callback void OnChanged() override { @@ -386,6 +387,8 @@ class PeerConnectionTestClient : public webrtc::PeerConnectionObserver, return true; } + bool received_media_packet() const { return received_media_packet_; } + void OnIceComplete() override { LOG(INFO) << id_ << "OnIceComplete"; } void OnDataChannel(DataChannelInterface* data_channel) override { @@ -924,6 +927,10 @@ class PeerConnectionTestClient : public webrtc::PeerConnectionObserver, rtc::scoped_refptr data_channel_; rtc::scoped_ptr data_observer_; + + // "true" if the PeerConnection indicated that a packet was received, + // through PeerConnectionObserverInterface. + bool received_media_packet_ = false; }; class P2PTestConductor : public testing::Test { @@ -1104,6 +1111,10 @@ class P2PTestConductor : public testing::Test { EXPECT_TRUE_WAIT(FramesNotPending(audio_frame_count, video_frame_count), kMaxWaitForFramesMs); + if (audio_frame_count != -1 || video_frame_count != -1) { + EXPECT_TRUE(initiating_client_->received_media_packet()); + EXPECT_TRUE(receiving_client_->received_media_packet()); + } } void SetupAndVerifyDtlsCall() { diff --git a/talk/app/webrtc/peerconnectioninterface.h b/talk/app/webrtc/peerconnectioninterface.h index e449dc4721..42b5ae0907 100644 --- a/talk/app/webrtc/peerconnectioninterface.h +++ b/talk/app/webrtc/peerconnectioninterface.h @@ -501,6 +501,9 @@ class PeerConnectionObserver { // Called when the ICE connection receiving status changes. virtual void OnIceConnectionReceivingChange(bool receiving) {} + // Called when the first RTP packet is received. + virtual void OnFirstMediaPacketReceived() {} + protected: // Dtor protected as objects shouldn't be deleted via this interface. ~PeerConnectionObserver() {} diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc index c6d1856f03..0270c554fb 100644 --- a/talk/app/webrtc/webrtcsession.cc +++ b/talk/app/webrtc/webrtcsession.cc @@ -1844,6 +1844,8 @@ bool WebRtcSession::CreateVoiceChannel(const cricket::ContentInfo* content) { voice_channel_->SignalDtlsSetupFailure.connect( this, &WebRtcSession::OnDtlsSetupFailure); + voice_channel_->SignalFirstPacketReceived.connect( + this, &WebRtcSession::OnChannelFirstPacketReceived); SignalVoiceChannelCreated(); voice_channel_->transport_channel()->SignalSentPacket.connect( @@ -1861,6 +1863,8 @@ bool WebRtcSession::CreateVideoChannel(const cricket::ContentInfo* content) { video_channel_->SignalDtlsSetupFailure.connect( this, &WebRtcSession::OnDtlsSetupFailure); + video_channel_->SignalFirstPacketReceived.connect( + this, &WebRtcSession::OnChannelFirstPacketReceived); SignalVideoChannelCreated(); video_channel_->transport_channel()->SignalSentPacket.connect( @@ -1895,6 +1899,14 @@ void WebRtcSession::OnDtlsSetupFailure(cricket::BaseChannel*, bool rtcp) { rtcp ? kDtlsSetupFailureRtcp : kDtlsSetupFailureRtp); } +void WebRtcSession::OnChannelFirstPacketReceived(cricket::BaseChannel*) { + ASSERT(signaling_thread()->IsCurrent()); + if (!has_received_media_packet_) { + has_received_media_packet_ = true; + SignalFirstMediaPacketReceived(); + } +} + void WebRtcSession::OnDataChannelMessageReceived( cricket::DataChannel* channel, const cricket::ReceiveDataParams& params, diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h index cd3f896726..3848014545 100644 --- a/talk/app/webrtc/webrtcsession.h +++ b/talk/app/webrtc/webrtcsession.h @@ -309,6 +309,7 @@ class WebRtcSession : public AudioProviderInterface, void OnCertificateReady( const rtc::scoped_refptr& certificate); void OnDtlsSetupFailure(cricket::BaseChannel*, bool rtcp); + void OnChannelFirstPacketReceived(cricket::BaseChannel*); // For unit test. bool waiting_for_certificate_for_testing() const; @@ -335,6 +336,9 @@ class WebRtcSession : public AudioProviderInterface, sigslot::signal2 SignalDataChannelOpenMessage; + // Called when the first RTP packet is received. + sigslot::signal0<> SignalFirstMediaPacketReceived; + private: // Indicates the type of SessionDescription in a call to SetLocalDescription // and SetRemoteDescription. @@ -518,6 +522,8 @@ class WebRtcSession : public AudioProviderInterface, // Declares the RTCP mux policy for the WebRTCSession. PeerConnectionInterface::RtcpMuxPolicy rtcp_mux_policy_; + bool has_received_media_packet_ = false; + RTC_DISALLOW_COPY_AND_ASSIGN(WebRtcSession); }; } // namespace webrtc diff --git a/webrtc/examples/androidapp/src/org/appspot/apprtc/PeerConnectionClient.java b/webrtc/examples/androidapp/src/org/appspot/apprtc/PeerConnectionClient.java index eb4d959067..91f46b2b87 100644 --- a/webrtc/examples/androidapp/src/org/appspot/apprtc/PeerConnectionClient.java +++ b/webrtc/examples/androidapp/src/org/appspot/apprtc/PeerConnectionClient.java @@ -1000,6 +1000,10 @@ public class PeerConnectionClient { // No need to do anything; AppRTC follows a pre-agreed-upon // signaling/negotiation protocol. } + + @Override + public void onFirstMediaPacketReceived() { + } } // Implementation detail: handle offer creation/signaling and answer setting, diff --git a/webrtc/examples/objc/AppRTCDemo/ARDAppClient.m b/webrtc/examples/objc/AppRTCDemo/ARDAppClient.m index 33e00ed443..50fac5d3b7 100644 --- a/webrtc/examples/objc/AppRTCDemo/ARDAppClient.m +++ b/webrtc/examples/objc/AppRTCDemo/ARDAppClient.m @@ -384,6 +384,11 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6; didOpenDataChannel:(RTCDataChannel *)dataChannel { } +- (void)peerConnectionOnFirstMediaPacketReceived: + (RTCPeerConnection *)peerConnection { + RTCLog(@"Received first media packet."); +} + #pragma mark - RTCStatsDelegate - (void)peerConnection:(RTCPeerConnection *)peerConnection