(this CL is based on the work by Taylor and Steve in https://webrtc-review.googlesource.com/c/src/+/10201) This SetAudioPlayout method lets applications disable audio playout while still processing incoming audio data and generating statistics on the received audio. This may be useful if the application wants to set up media flows as soon as possible, but isn't ready to play audio yet. Currently, native applications don't have any API point to control this, unless they completely implement their own AudioDeviceModule. The SetAudioRecording works in a similar fashion but for the recorded audio. One difference is that calling SetAudioRecording(false) does not keep any audio processing alive. TBR=solenberg Bug: webrtc:7313 Change-Id: I0aa075f6bfef9818f1080f85a8ff7842fb0750aa Reviewed-on: https://webrtc-review.googlesource.com/16180 Reviewed-by: Henrik Andreassson <henrika@webrtc.org> Reviewed-by: Karl Wiberg <kwiberg@webrtc.org> Commit-Queue: Henrik Andreassson <henrika@webrtc.org> Cr-Commit-Position: refs/heads/master@{#20499}
570 lines
20 KiB
Java
570 lines
20 KiB
Java
/*
|
|
* Copyright 2013 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
package org.webrtc;
|
|
|
|
import java.util.Collections;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Java-land version of the PeerConnection APIs; wraps the C++ API
|
|
* http://www.webrtc.org/reference/native-apis, which in turn is inspired by the
|
|
* JS APIs: http://dev.w3.org/2011/webrtc/editor/webrtc.html and
|
|
* http://www.w3.org/TR/mediacapture-streams/
|
|
*/
|
|
public class PeerConnection {
|
|
static {
|
|
System.loadLibrary("jingle_peerconnection_so");
|
|
}
|
|
|
|
/** Tracks PeerConnectionInterface::IceGatheringState */
|
|
public enum IceGatheringState { NEW, GATHERING, COMPLETE }
|
|
|
|
/** Tracks PeerConnectionInterface::IceConnectionState */
|
|
public enum IceConnectionState {
|
|
NEW,
|
|
CHECKING,
|
|
CONNECTED,
|
|
COMPLETED,
|
|
FAILED,
|
|
DISCONNECTED,
|
|
CLOSED
|
|
}
|
|
|
|
/** Tracks PeerConnectionInterface::TlsCertPolicy */
|
|
public enum TlsCertPolicy {
|
|
TLS_CERT_POLICY_SECURE,
|
|
TLS_CERT_POLICY_INSECURE_NO_CHECK,
|
|
}
|
|
|
|
/** Tracks PeerConnectionInterface::SignalingState */
|
|
public enum SignalingState {
|
|
STABLE,
|
|
HAVE_LOCAL_OFFER,
|
|
HAVE_LOCAL_PRANSWER,
|
|
HAVE_REMOTE_OFFER,
|
|
HAVE_REMOTE_PRANSWER,
|
|
CLOSED
|
|
}
|
|
|
|
/** Java version of PeerConnectionObserver. */
|
|
public static interface Observer {
|
|
/** Triggered when the SignalingState changes. */
|
|
public void onSignalingChange(SignalingState newState);
|
|
|
|
/** Triggered when the IceConnectionState changes. */
|
|
public void onIceConnectionChange(IceConnectionState newState);
|
|
|
|
/** Triggered when the ICE connection receiving status changes. */
|
|
public void onIceConnectionReceivingChange(boolean receiving);
|
|
|
|
/** Triggered when the IceGatheringState changes. */
|
|
public void onIceGatheringChange(IceGatheringState newState);
|
|
|
|
/** Triggered when a new ICE candidate has been found. */
|
|
public void onIceCandidate(IceCandidate candidate);
|
|
|
|
/** Triggered when some ICE candidates have been removed. */
|
|
public void onIceCandidatesRemoved(IceCandidate[] candidates);
|
|
|
|
/** Triggered when media is received on a new stream from remote peer. */
|
|
public void onAddStream(MediaStream stream);
|
|
|
|
/** Triggered when a remote peer close a stream. */
|
|
public void onRemoveStream(MediaStream stream);
|
|
|
|
/** Triggered when a remote peer opens a DataChannel. */
|
|
public void onDataChannel(DataChannel dataChannel);
|
|
|
|
/** Triggered when renegotiation is necessary. */
|
|
public void onRenegotiationNeeded();
|
|
|
|
/**
|
|
* Triggered when a new track is signaled by the remote peer, as a result of
|
|
* setRemoteDescription.
|
|
*/
|
|
public void onAddTrack(RtpReceiver receiver, MediaStream[] mediaStreams);
|
|
}
|
|
|
|
/** Java version of PeerConnectionInterface.IceServer. */
|
|
public static class IceServer {
|
|
// List of URIs associated with this server. Valid formats are described
|
|
// in RFC7064 and RFC7065, and more may be added in the future. The "host"
|
|
// part of the URI may contain either an IP address or a hostname.
|
|
@Deprecated public final String uri;
|
|
public final List<String> urls;
|
|
public final String username;
|
|
public final String password;
|
|
public final TlsCertPolicy tlsCertPolicy;
|
|
|
|
// If the URIs in |urls| only contain IP addresses, this field can be used
|
|
// to indicate the hostname, which may be necessary for TLS (using the SNI
|
|
// extension). If |urls| itself contains the hostname, this isn't
|
|
// necessary.
|
|
public final String hostname;
|
|
|
|
// List of protocols to be used in the TLS ALPN extension.
|
|
public final List<String> tlsAlpnProtocols;
|
|
|
|
// List of elliptic curves to be used in the TLS elliptic curves extension.
|
|
// Only curve names supported by OpenSSL should be used (eg. "P-256","X25519").
|
|
public final List<String> tlsEllipticCurves;
|
|
|
|
/** Convenience constructor for STUN servers. */
|
|
@Deprecated
|
|
public IceServer(String uri) {
|
|
this(uri, "", "");
|
|
}
|
|
|
|
@Deprecated
|
|
public IceServer(String uri, String username, String password) {
|
|
this(uri, username, password, TlsCertPolicy.TLS_CERT_POLICY_SECURE);
|
|
}
|
|
|
|
@Deprecated
|
|
public IceServer(String uri, String username, String password, TlsCertPolicy tlsCertPolicy) {
|
|
this(uri, username, password, tlsCertPolicy, "");
|
|
}
|
|
|
|
@Deprecated
|
|
public IceServer(String uri, String username, String password, TlsCertPolicy tlsCertPolicy,
|
|
String hostname) {
|
|
this(uri, Collections.singletonList(uri), username, password, tlsCertPolicy, hostname, null,
|
|
null);
|
|
}
|
|
|
|
private IceServer(String uri, List<String> urls, String username, String password,
|
|
TlsCertPolicy tlsCertPolicy, String hostname, List<String> tlsAlpnProtocols,
|
|
List<String> tlsEllipticCurves) {
|
|
if (uri == null || urls == null || urls.isEmpty()) {
|
|
throw new IllegalArgumentException("uri == null || urls == null || urls.isEmpty()");
|
|
}
|
|
for (String it : urls) {
|
|
if (it == null) {
|
|
throw new IllegalArgumentException("urls element is null: " + urls);
|
|
}
|
|
}
|
|
if (username == null) {
|
|
throw new IllegalArgumentException("username == null");
|
|
}
|
|
if (password == null) {
|
|
throw new IllegalArgumentException("password == null");
|
|
}
|
|
if (hostname == null) {
|
|
throw new IllegalArgumentException("hostname == null");
|
|
}
|
|
this.uri = uri;
|
|
this.urls = urls;
|
|
this.username = username;
|
|
this.password = password;
|
|
this.tlsCertPolicy = tlsCertPolicy;
|
|
this.hostname = hostname;
|
|
this.tlsAlpnProtocols = tlsAlpnProtocols;
|
|
this.tlsEllipticCurves = tlsEllipticCurves;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return urls + " [" + username + ":" + password + "] [" + tlsCertPolicy + "] [" + hostname
|
|
+ "] [" + tlsAlpnProtocols + "] [" + tlsEllipticCurves + "]";
|
|
}
|
|
|
|
public static Builder builder(String uri) {
|
|
return new Builder(Collections.singletonList(uri));
|
|
}
|
|
|
|
public static Builder builder(List<String> urls) {
|
|
return new Builder(urls);
|
|
}
|
|
|
|
public static class Builder {
|
|
private final List<String> urls;
|
|
private String username = "";
|
|
private String password = "";
|
|
private TlsCertPolicy tlsCertPolicy = TlsCertPolicy.TLS_CERT_POLICY_SECURE;
|
|
private String hostname = "";
|
|
private List<String> tlsAlpnProtocols;
|
|
private List<String> tlsEllipticCurves;
|
|
|
|
private Builder(List<String> urls) {
|
|
if (urls == null || urls.isEmpty()) {
|
|
throw new IllegalArgumentException("urls == null || urls.isEmpty(): " + urls);
|
|
}
|
|
this.urls = urls;
|
|
}
|
|
|
|
public Builder setUsername(String username) {
|
|
this.username = username;
|
|
return this;
|
|
}
|
|
|
|
public Builder setPassword(String password) {
|
|
this.password = password;
|
|
return this;
|
|
}
|
|
|
|
public Builder setTlsCertPolicy(TlsCertPolicy tlsCertPolicy) {
|
|
this.tlsCertPolicy = tlsCertPolicy;
|
|
return this;
|
|
}
|
|
|
|
public Builder setHostname(String hostname) {
|
|
this.hostname = hostname;
|
|
return this;
|
|
}
|
|
|
|
public Builder setTlsAlpnProtocols(List<String> tlsAlpnProtocols) {
|
|
this.tlsAlpnProtocols = tlsAlpnProtocols;
|
|
return this;
|
|
}
|
|
|
|
public Builder setTlsEllipticCurves(List<String> tlsEllipticCurves) {
|
|
this.tlsEllipticCurves = tlsEllipticCurves;
|
|
return this;
|
|
}
|
|
|
|
public IceServer createIceServer() {
|
|
return new IceServer(urls.get(0), urls, username, password, tlsCertPolicy, hostname,
|
|
tlsAlpnProtocols, tlsEllipticCurves);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Java version of PeerConnectionInterface.IceTransportsType */
|
|
public enum IceTransportsType { NONE, RELAY, NOHOST, ALL }
|
|
|
|
/** Java version of PeerConnectionInterface.BundlePolicy */
|
|
public enum BundlePolicy { BALANCED, MAXBUNDLE, MAXCOMPAT }
|
|
|
|
/** Java version of PeerConnectionInterface.RtcpMuxPolicy */
|
|
public enum RtcpMuxPolicy { NEGOTIATE, REQUIRE }
|
|
|
|
/** Java version of PeerConnectionInterface.TcpCandidatePolicy */
|
|
public enum TcpCandidatePolicy { ENABLED, DISABLED }
|
|
|
|
/** Java version of PeerConnectionInterface.CandidateNetworkPolicy */
|
|
public enum CandidateNetworkPolicy { ALL, LOW_COST }
|
|
|
|
/** Java version of rtc::KeyType */
|
|
public enum KeyType { RSA, ECDSA }
|
|
|
|
/** Java version of PeerConnectionInterface.ContinualGatheringPolicy */
|
|
public enum ContinualGatheringPolicy { GATHER_ONCE, GATHER_CONTINUALLY }
|
|
|
|
/** Java version of rtc::IntervalRange */
|
|
public static class IntervalRange {
|
|
private final int min;
|
|
private final int max;
|
|
|
|
public IntervalRange(int min, int max) {
|
|
this.min = min;
|
|
this.max = max;
|
|
}
|
|
|
|
public int getMin() {
|
|
return min;
|
|
}
|
|
|
|
public int getMax() {
|
|
return max;
|
|
}
|
|
}
|
|
|
|
/** Java version of PeerConnectionInterface.RTCConfiguration */
|
|
public static class RTCConfiguration {
|
|
public IceTransportsType iceTransportsType;
|
|
public List<IceServer> iceServers;
|
|
public BundlePolicy bundlePolicy;
|
|
public RtcpMuxPolicy rtcpMuxPolicy;
|
|
public TcpCandidatePolicy tcpCandidatePolicy;
|
|
public CandidateNetworkPolicy candidateNetworkPolicy;
|
|
public int audioJitterBufferMaxPackets;
|
|
public boolean audioJitterBufferFastAccelerate;
|
|
public int iceConnectionReceivingTimeout;
|
|
public int iceBackupCandidatePairPingInterval;
|
|
public KeyType keyType;
|
|
public ContinualGatheringPolicy continualGatheringPolicy;
|
|
public int iceCandidatePoolSize;
|
|
public boolean pruneTurnPorts;
|
|
public boolean presumeWritableWhenFullyRelayed;
|
|
public Integer iceCheckMinInterval;
|
|
public boolean disableIPv6OnWifi;
|
|
// By default, PeerConnection will use a limited number of IPv6 network
|
|
// interfaces, in order to avoid too many ICE candidate pairs being created
|
|
// and delaying ICE completion.
|
|
//
|
|
// Can be set to Integer.MAX_VALUE to effectively disable the limit.
|
|
public int maxIPv6Networks;
|
|
public IntervalRange iceRegatherIntervalRange;
|
|
|
|
// This is an optional wrapper for the C++ webrtc::TurnCustomizer.
|
|
public TurnCustomizer turnCustomizer;
|
|
|
|
// TODO(deadbeef): Instead of duplicating the defaults here, we should do
|
|
// something to pick up the defaults from C++. The Objective-C equivalent
|
|
// of RTCConfiguration does that.
|
|
public RTCConfiguration(List<IceServer> iceServers) {
|
|
iceTransportsType = IceTransportsType.ALL;
|
|
bundlePolicy = BundlePolicy.BALANCED;
|
|
rtcpMuxPolicy = RtcpMuxPolicy.REQUIRE;
|
|
tcpCandidatePolicy = TcpCandidatePolicy.ENABLED;
|
|
candidateNetworkPolicy = CandidateNetworkPolicy.ALL;
|
|
this.iceServers = iceServers;
|
|
audioJitterBufferMaxPackets = 50;
|
|
audioJitterBufferFastAccelerate = false;
|
|
iceConnectionReceivingTimeout = -1;
|
|
iceBackupCandidatePairPingInterval = -1;
|
|
keyType = KeyType.ECDSA;
|
|
continualGatheringPolicy = ContinualGatheringPolicy.GATHER_ONCE;
|
|
iceCandidatePoolSize = 0;
|
|
pruneTurnPorts = false;
|
|
presumeWritableWhenFullyRelayed = false;
|
|
iceCheckMinInterval = null;
|
|
disableIPv6OnWifi = false;
|
|
maxIPv6Networks = 5;
|
|
iceRegatherIntervalRange = null;
|
|
}
|
|
};
|
|
|
|
private final List<MediaStream> localStreams;
|
|
private final long nativePeerConnection;
|
|
private final long nativeObserver;
|
|
private List<RtpSender> senders;
|
|
private List<RtpReceiver> receivers;
|
|
|
|
PeerConnection(long nativePeerConnection, long nativeObserver) {
|
|
this.nativePeerConnection = nativePeerConnection;
|
|
this.nativeObserver = nativeObserver;
|
|
localStreams = new LinkedList<MediaStream>();
|
|
senders = new LinkedList<RtpSender>();
|
|
receivers = new LinkedList<RtpReceiver>();
|
|
}
|
|
|
|
// JsepInterface.
|
|
public native SessionDescription getLocalDescription();
|
|
|
|
public native SessionDescription getRemoteDescription();
|
|
|
|
public native DataChannel createDataChannel(String label, DataChannel.Init init);
|
|
|
|
public native void createOffer(SdpObserver observer, MediaConstraints constraints);
|
|
|
|
public native void createAnswer(SdpObserver observer, MediaConstraints constraints);
|
|
|
|
public native void setLocalDescription(SdpObserver observer, SessionDescription sdp);
|
|
|
|
public native void setRemoteDescription(SdpObserver observer, SessionDescription sdp);
|
|
|
|
// True if remote audio should be played out. Defaults to true.
|
|
// Note that even if playout is enabled, streams will only be played out if
|
|
// the appropriate SDP is also applied. The main purpose of this API is to
|
|
// be able to control the exact time when audio playout starts.
|
|
public native void setAudioPlayout(boolean playout);
|
|
|
|
// True if local audio shall be recorded. Defaults to true.
|
|
// Note that even if recording is enabled, streams will only be recorded if
|
|
// the appropriate SDP is also applied. The main purpose of this API is to
|
|
// be able to control the exact time when audio recording starts.
|
|
public native void setAudioRecording(boolean recording);
|
|
|
|
public boolean setConfiguration(RTCConfiguration config) {
|
|
return nativeSetConfiguration(config, nativeObserver);
|
|
}
|
|
|
|
public boolean addIceCandidate(IceCandidate candidate) {
|
|
return nativeAddIceCandidate(candidate.sdpMid, candidate.sdpMLineIndex, candidate.sdp);
|
|
}
|
|
|
|
public boolean removeIceCandidates(final IceCandidate[] candidates) {
|
|
return nativeRemoveIceCandidates(candidates);
|
|
}
|
|
|
|
public boolean addStream(MediaStream stream) {
|
|
boolean ret = nativeAddLocalStream(stream.nativeStream);
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
localStreams.add(stream);
|
|
return true;
|
|
}
|
|
|
|
public void removeStream(MediaStream stream) {
|
|
nativeRemoveLocalStream(stream.nativeStream);
|
|
localStreams.remove(stream);
|
|
}
|
|
|
|
/**
|
|
* Creates an RtpSender without a track.
|
|
* <p>
|
|
* This method allows an application to cause the PeerConnection to negotiate
|
|
* sending/receiving a specific media type, but without having a track to
|
|
* send yet.
|
|
* <p>
|
|
* When the application does want to begin sending a track, it can call
|
|
* RtpSender.setTrack, which doesn't require any additional SDP negotiation.
|
|
* <p>
|
|
* Example use:
|
|
* <pre>
|
|
* {@code
|
|
* audioSender = pc.createSender("audio", "stream1");
|
|
* videoSender = pc.createSender("video", "stream1");
|
|
* // Do normal SDP offer/answer, which will kick off ICE/DTLS and negotiate
|
|
* // media parameters....
|
|
* // Later, when the endpoint is ready to actually begin sending:
|
|
* audioSender.setTrack(audioTrack, false);
|
|
* videoSender.setTrack(videoTrack, false);
|
|
* }
|
|
* </pre>
|
|
* Note: This corresponds most closely to "addTransceiver" in the official
|
|
* WebRTC API, in that it creates a sender without a track. It was
|
|
* implemented before addTransceiver because it provides useful
|
|
* functionality, and properly implementing transceivers would have required
|
|
* a great deal more work.
|
|
*
|
|
* @param kind Corresponds to MediaStreamTrack kinds (must be "audio" or
|
|
* "video").
|
|
* @param stream_id The ID of the MediaStream that this sender's track will
|
|
* be associated with when SDP is applied to the remote
|
|
* PeerConnection. If createSender is used to create an
|
|
* audio and video sender that should be synchronized, they
|
|
* should use the same stream ID.
|
|
* @return A new RtpSender object if successful, or null otherwise.
|
|
*/
|
|
public RtpSender createSender(String kind, String stream_id) {
|
|
RtpSender new_sender = nativeCreateSender(kind, stream_id);
|
|
if (new_sender != null) {
|
|
senders.add(new_sender);
|
|
}
|
|
return new_sender;
|
|
}
|
|
|
|
// Note that calling getSenders will dispose of the senders previously
|
|
// returned (and same goes for getReceivers).
|
|
public List<RtpSender> getSenders() {
|
|
for (RtpSender sender : senders) {
|
|
sender.dispose();
|
|
}
|
|
senders = nativeGetSenders();
|
|
return Collections.unmodifiableList(senders);
|
|
}
|
|
|
|
public List<RtpReceiver> getReceivers() {
|
|
for (RtpReceiver receiver : receivers) {
|
|
receiver.dispose();
|
|
}
|
|
receivers = nativeGetReceivers();
|
|
return Collections.unmodifiableList(receivers);
|
|
}
|
|
|
|
// Older, non-standard implementation of getStats.
|
|
@Deprecated
|
|
public boolean getStats(StatsObserver observer, MediaStreamTrack track) {
|
|
return nativeOldGetStats(observer, (track == null) ? 0 : track.nativeTrack);
|
|
}
|
|
|
|
// Gets stats using the new stats collection API, see webrtc/api/stats/. These
|
|
// will replace old stats collection API when the new API has matured enough.
|
|
public void getStats(RTCStatsCollectorCallback callback) {
|
|
nativeNewGetStats(callback);
|
|
}
|
|
|
|
// Limits the bandwidth allocated for all RTP streams sent by this
|
|
// PeerConnection. Pass null to leave a value unchanged.
|
|
public native boolean setBitrate(Integer min, Integer current, Integer max);
|
|
|
|
// Starts recording an RTC event log. Ownership of the file is transfered to
|
|
// the native code. If an RTC event log is already being recorded, it will be
|
|
// stopped and a new one will start using the provided file. Logging will
|
|
// continue until the stopRtcEventLog function is called. The max_size_bytes
|
|
// argument is ignored, it is added for future use.
|
|
public boolean startRtcEventLog(int file_descriptor, int max_size_bytes) {
|
|
return nativeStartRtcEventLog(file_descriptor, max_size_bytes);
|
|
}
|
|
|
|
// Stops recording an RTC event log. If no RTC event log is currently being
|
|
// recorded, this call will have no effect.
|
|
public void stopRtcEventLog() {
|
|
nativeStopRtcEventLog();
|
|
}
|
|
|
|
// TODO(fischman): add support for DTMF-related methods once that API
|
|
// stabilizes.
|
|
public native SignalingState signalingState();
|
|
|
|
public native IceConnectionState iceConnectionState();
|
|
|
|
public native IceGatheringState iceGatheringState();
|
|
|
|
public native void close();
|
|
|
|
/**
|
|
* Free native resources associated with this PeerConnection instance.
|
|
* <p>
|
|
* This method removes a reference count from the C++ PeerConnection object,
|
|
* which should result in it being destroyed. It also calls equivalent
|
|
* "dispose" methods on the Java objects attached to this PeerConnection
|
|
* (streams, senders, receivers), such that their associated C++ objects
|
|
* will also be destroyed.
|
|
* <p>
|
|
* Note that this method cannot be safely called from an observer callback
|
|
* (PeerConnection.Observer, DataChannel.Observer, etc.). If you want to, for
|
|
* example, destroy the PeerConnection after an "ICE failed" callback, you
|
|
* must do this asynchronously (in other words, unwind the stack first). See
|
|
* <a href="https://bugs.chromium.org/p/webrtc/issues/detail?id=3721">bug
|
|
* 3721</a> for more details.
|
|
*/
|
|
public void dispose() {
|
|
close();
|
|
for (MediaStream stream : localStreams) {
|
|
nativeRemoveLocalStream(stream.nativeStream);
|
|
stream.dispose();
|
|
}
|
|
localStreams.clear();
|
|
for (RtpSender sender : senders) {
|
|
sender.dispose();
|
|
}
|
|
senders.clear();
|
|
for (RtpReceiver receiver : receivers) {
|
|
receiver.dispose();
|
|
}
|
|
receivers.clear();
|
|
JniCommon.nativeReleaseRef(nativePeerConnection);
|
|
freeObserver(nativeObserver);
|
|
}
|
|
|
|
private static native void freeObserver(long nativeObserver);
|
|
|
|
public native boolean nativeSetConfiguration(RTCConfiguration config, long nativeObserver);
|
|
|
|
private native boolean nativeAddIceCandidate(
|
|
String sdpMid, int sdpMLineIndex, String iceCandidateSdp);
|
|
|
|
private native boolean nativeRemoveIceCandidates(final IceCandidate[] candidates);
|
|
|
|
private native boolean nativeAddLocalStream(long nativeStream);
|
|
|
|
private native void nativeRemoveLocalStream(long nativeStream);
|
|
|
|
private native boolean nativeOldGetStats(StatsObserver observer, long nativeTrack);
|
|
|
|
private native void nativeNewGetStats(RTCStatsCollectorCallback callback);
|
|
|
|
private native RtpSender nativeCreateSender(String kind, String stream_id);
|
|
|
|
private native List<RtpSender> nativeGetSenders();
|
|
|
|
private native List<RtpReceiver> nativeGetReceivers();
|
|
|
|
private native boolean nativeStartRtcEventLog(int file_descriptor, int max_size_bytes);
|
|
|
|
private native void nativeStopRtcEventLog();
|
|
}
|