Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,22 @@ void publish(String streamId, String token, boolean videoCallEnabled, boolean au
*/
void setAudioEnabled(boolean enabled);

/**
* Disable black frame sender when video is toggled off via toggleSendVideo(false).
* When true, no black frames will be sent when the camera is turned off during a call.
*
* @param disable true to disable black frame sender, false to enable (default)
*/
void setDisableBlackFrameSender(boolean disable);

/**
* Disable silence packets when audio is muted via toggleSendAudio(false).
* When true, RTP transmission is stopped when muted (no silence packets sent).
*
* @param disable true to stop RTP when muted, false for default behavior (sends silence)
*/
void setDisableSilenceWhenMuted(boolean disable);

/**
* enable/disable played track stream from the server
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,17 @@ public class WebRTCClientConfig {
* Flag for connecting bluetooth headphones.
*/
public boolean bluetoothEnabled = false;

/*
* Flag indicating whether black frame sender is disabled when video is toggled off.
* When true, no black frames will be sent when toggleSendVideo(false) is called.
*/
public boolean disableBlackFrameSender = false;

/*
* Flag indicating whether silence packets are disabled when audio is muted.
* When true, RTP transmission is stopped when toggleSendAudio(false) is called (no silence packets sent).
* When false, muted audio still sends silence packets (default WebRTC behavior).
*/
public boolean disableSilenceWhenMuted = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -1853,23 +1853,50 @@ public void sendMessageViaDataChannel(String streamId, DataChannel.Buffer buffer
}

public void changeVideoCapturer(VideoCapturer newVideoCapturer) {
try {
if (videoCapturer != null) {
videoCapturer.stopCapture();
}
} catch (InterruptedException e) {
e.printStackTrace();
}

videoCapturerStopped = true;
VideoCapturer oldVideoCapturer = videoCapturer;
VideoTrack oldVideoTrack = localVideoTrack;
videoCapturer = newVideoCapturer;
localVideoTrack = null;

MediaStreamTrack newTrack = createVideoTrack(videoCapturer);
if (localVideoSender != null) {
localVideoSender.setTrack(newTrack, true);
RtpSender videoSender = findVideoSender();
if (videoSender == null || newTrack == null) {
Log.e(TAG, "Video sender or new video track is not available while changing video capturer.");
stopVideoCapturer(newVideoCapturer);
videoCapturer = oldVideoCapturer;
localVideoTrack = oldVideoTrack;
return;
}

try {
if (!videoSender.setTrack(newTrack, true)) {
Log.e(TAG, "RtpSender.setTrack failed while changing video capturer.");
stopVideoCapturer(newVideoCapturer);
videoCapturer = oldVideoCapturer;
localVideoTrack = oldVideoTrack;
return;
}
} catch (IllegalStateException e) {
localVideoSender = null;
stopVideoCapturer(newVideoCapturer);
videoCapturer = oldVideoCapturer;
localVideoTrack = oldVideoTrack;
Log.e(TAG, "Video sender was disposed while changing video capturer.", e);
return;
}

stopVideoCapturer(oldVideoCapturer);

}

private void stopVideoCapturer(@androidx.annotation.Nullable VideoCapturer capturer) {
try {
if (capturer != null) {
capturer.stopCapture();
}
} catch (InterruptedException e) {
Log.e(TAG, "Could not stop video capturer.", e);
}
}

/**
Expand Down Expand Up @@ -2133,15 +2160,21 @@ public void initDataChannel(String streamId) {
}

public void setDegradationPreference(RtpParameters.DegradationPreference degradationPreference) {
if (localVideoSender == null) {
Log.w(TAG, "Sender is not ready.");
return;
}
executor.execute(() -> {
RtpParameters newParameters = localVideoSender.getParameters();
if (newParameters != null) {
newParameters.degradationPreference = degradationPreference;
localVideoSender.setParameters(newParameters);
RtpSender videoSender = findVideoSender();
if (videoSender == null) {
Log.w(TAG, "Sender is not ready.");
return;
}
try {
RtpParameters newParameters = videoSender.getParameters();
if (newParameters != null) {
newParameters.degradationPreference = degradationPreference;
videoSender.setParameters(newParameters);
}
} catch (IllegalStateException e) {
localVideoSender = null;
Log.w(TAG, "Video sender is not available: " + e.getMessage());
}
});
}
Expand Down Expand Up @@ -2262,8 +2295,12 @@ public void setAudioEnabled(final boolean enable) {
config.audioCallEnabled = enable;

executor.execute(() -> {
boolean shouldSendAudio = enable && sendAudioEnabled;
if (localAudioTrack != null) {
localAudioTrack.setEnabled(enable);
localAudioTrack.setEnabled(shouldSendAudio);
}
if (config.disableSilenceWhenMuted) {
setAudioSenderEnabled(shouldSendAudio);
}
});
}
Expand All @@ -2286,8 +2323,12 @@ public void setVideoEnabled(final boolean enable) {
public void toggleSendAudio(boolean enableAudio) {
executor.execute(() -> {
sendAudioEnabled = enableAudio;
boolean shouldSendAudio = enableAudio && config.audioCallEnabled;
if (localAudioTrack != null) {
localAudioTrack.setEnabled(enableAudio);
localAudioTrack.setEnabled(shouldSendAudio);
}
if (config.disableSilenceWhenMuted) {
setAudioSenderEnabled(shouldSendAudio);
}
});
}
Expand All @@ -2307,8 +2348,10 @@ public void toggleSendVideo(boolean enableVideo) {
changeVideoSource(StreamSource.FRONT_CAMERA);
} else {
changeVideoSource(StreamSource.CUSTOM);
blackFrameSender = new BlackFrameSender((CustomVideoCapturer) getVideoCapturer());
blackFrameSender.start();
if (!config.disableBlackFrameSender) {
blackFrameSender = new BlackFrameSender((CustomVideoCapturer) getVideoCapturer());
blackFrameSender.start();
}
}
});
}
Expand Down Expand Up @@ -2444,28 +2487,32 @@ public void setVideoMaxBitrate(@androidx.annotation.Nullable final Integer maxBi
return;
}
executor.execute(() -> {
if (localVideoSender == null) {
return;
}
Log.d(TAG, "Requested max video bitrate: " + maxBitrateKbps);
if (localVideoSender == null) {
RtpSender videoSender = findVideoSender();
if (videoSender == null) {
Log.w(TAG, "Sender is not ready.");
return;
}

RtpParameters parameters = localVideoSender.getParameters();
if (parameters.encodings.isEmpty()) {
Log.w(TAG, "RtpParameters are not ready.");
return;
}
try {
RtpParameters parameters = videoSender.getParameters();
if (parameters.encodings.isEmpty()) {
Log.w(TAG, "RtpParameters are not ready.");
return;
}

for (RtpParameters.Encoding encoding : parameters.encodings) {
// Null value means no limit.
encoding.maxBitrateBps = maxBitrateKbps == null ? null : maxBitrateKbps * BPS_IN_KBPS;
encoding.minBitrateBps = maxBitrateKbps == null ? null : maxBitrateKbps * BPS_IN_KBPS / 2;
}
if (!localVideoSender.setParameters(parameters)) {
Log.e(TAG, "RtpSender.setParameters failed.");
for (RtpParameters.Encoding encoding : parameters.encodings) {
// Null value means no limit.
encoding.maxBitrateBps = maxBitrateKbps == null ? null : maxBitrateKbps * BPS_IN_KBPS;
encoding.minBitrateBps = maxBitrateKbps == null ? null : maxBitrateKbps * BPS_IN_KBPS / 2;
}
if (!videoSender.setParameters(parameters)) {
Log.e(TAG, "RtpSender.setParameters failed.");
}
} catch (IllegalStateException e) {
localVideoSender = null;
Log.w(TAG, "Video sender is not available: " + e.getMessage());
return;
}
Log.d(TAG, "Configured max video bitrate to: " + maxBitrateKbps);
});
Expand Down Expand Up @@ -2499,7 +2546,53 @@ private VideoTrack createVideoTrack(VideoCapturer capturer) {

private void findVideoSender(String streamId) {
PeerConnection pc = getPeerConnectionFor(streamId);
findVideoSender(pc);
}

private void setAudioSenderEnabled(boolean enabled) {
for (PeerInfo peerInfo : peers.values()) {
PeerConnection pc = peerInfo.peerConnection;
if (pc == null) {
continue;
}
try {
for (RtpSender sender : pc.getSenders()) {
MediaStreamTrack track = sender.track();
if (track == null || !MediaStreamTrack.AUDIO_TRACK_KIND.equals(track.kind())) {
continue;
}
RtpParameters parameters = sender.getParameters();
if (parameters == null || parameters.encodings.isEmpty()) {
Log.w(TAG, "Audio RtpParameters are not ready.");
return;
}
for (RtpParameters.Encoding encoding : parameters.encodings) {
encoding.active = enabled;
}
if (!sender.setParameters(parameters)) {
Log.e(TAG, "Audio RtpSender.setParameters failed.");
}
return;
}
} catch (IllegalStateException e) {
Log.w(TAG, "Audio sender is not available: " + e.getMessage());
}
}
}

@androidx.annotation.Nullable
private RtpSender findVideoSender() {
for (PeerInfo peerInfo : peers.values()) {
RtpSender sender = findVideoSender(peerInfo.peerConnection);
if (sender != null) {
return sender;
}
}
return null;
}

@androidx.annotation.Nullable
private RtpSender findVideoSender(@androidx.annotation.Nullable PeerConnection pc) {
if (pc != null) {
for (RtpSender sender : pc.getSenders()) {
MediaStreamTrack track = sender.track();
Expand All @@ -2508,10 +2601,12 @@ private void findVideoSender(String streamId) {
if (trackType.equals(VIDEO_TRACK_TYPE)) {
Log.d(TAG, "Found video sender.");
localVideoSender = sender;
return sender;
}
}
}
}
return null;
}

private static String getSdpVideoCodecName(String codec) {
Expand Down Expand Up @@ -2833,6 +2928,14 @@ public void setDataChannelEnabled(boolean dataChannelEnabled) {
this.config.dataChannelEnabled = dataChannelEnabled;
}

public void setDisableBlackFrameSender(boolean disableBlackFrameSender) {
this.config.disableBlackFrameSender = disableBlackFrameSender;
}

public void setDisableSilenceWhenMuted(boolean disableSilenceWhenMuted) {
this.config.disableSilenceWhenMuted = disableSilenceWhenMuted;
}

public void setFactory(@androidx.annotation.Nullable PeerConnectionFactory factory) {
this.factory = factory;
}
Expand Down
Loading
Loading