|
94 | 94 | import java.util.Map; |
95 | 95 | import java.util.Map.Entry; |
96 | 96 | import java.util.UUID; |
| 97 | +import java.util.concurrent.ConcurrentHashMap; |
97 | 98 | import java.util.concurrent.ExecutorService; |
98 | 99 | import java.util.concurrent.Executors; |
99 | 100 |
|
@@ -146,6 +147,9 @@ public class MethodCallHandlerImpl implements MethodCallHandler, StateProvider { |
146 | 147 | public AudioProcessingFactoryProvider audioProcessingFactoryProvider; |
147 | 148 |
|
148 | 149 | private ConstraintsMap initializedAndroidAudioConfiguration; |
| 150 | + private final Map<String, Double> trackVolumeCache = new ConcurrentHashMap<>(); |
| 151 | + private final Map<String, Double> pausedTrackVolumes = new ConcurrentHashMap<>(); |
| 152 | + private volatile boolean isAudioPlayoutPaused = false; |
149 | 153 |
|
150 | 154 | public static class LogSink implements Loggable { |
151 | 155 | @Override |
@@ -1147,6 +1151,24 @@ public void onInterruptionEnd() { |
1147 | 1151 | } |
1148 | 1152 | break; |
1149 | 1153 | } |
| 1154 | + case "pauseAudioPlayout": { |
| 1155 | + executor.execute(() -> { |
| 1156 | + pauseAudioPlayoutInternal(); |
| 1157 | + mainHandler.post(() -> { |
| 1158 | + result.success(null); |
| 1159 | + }); |
| 1160 | + }); |
| 1161 | + break; |
| 1162 | + } |
| 1163 | + case "resumeAudioPlayout": { |
| 1164 | + executor.execute(() -> { |
| 1165 | + resumeAudioPlayoutInternal(); |
| 1166 | + mainHandler.post(() -> { |
| 1167 | + result.success(null); |
| 1168 | + }); |
| 1169 | + }); |
| 1170 | + break; |
| 1171 | + } |
1150 | 1172 | case "startLocalRecording": { |
1151 | 1173 | executor.execute(() -> { |
1152 | 1174 | audioDeviceModule.prewarmRecording(); |
@@ -1222,6 +1244,53 @@ private PeerConnection getPeerConnection(String id) { |
1222 | 1244 | return (pco == null) ? null : pco.getPeerConnection(); |
1223 | 1245 | } |
1224 | 1246 |
|
| 1247 | + private void pauseAudioPlayoutInternal() { |
| 1248 | + isAudioPlayoutPaused = true; |
| 1249 | + |
| 1250 | + for (PeerConnectionObserver observer : mPeerConnectionObservers.values()) { |
| 1251 | + for (Map.Entry<String, MediaStreamTrack> entry : observer.remoteTracks.entrySet()) { |
| 1252 | + MediaStreamTrack track = entry.getValue(); |
| 1253 | + if (track instanceof AudioTrack) { |
| 1254 | + String trackId = track.id(); |
| 1255 | + if (!pausedTrackVolumes.containsKey(trackId)) { |
| 1256 | + double previousVolume = trackVolumeCache.getOrDefault(trackId, 1.0); |
| 1257 | + pausedTrackVolumes.put(trackId, previousVolume); |
| 1258 | + } |
| 1259 | + try { |
| 1260 | + ((AudioTrack) track).setVolume(0.0); |
| 1261 | + } catch (Exception e) { |
| 1262 | + Log.e(TAG, "pauseAudioPlayoutInternal: setVolume failed for track " + track.id(), e); |
| 1263 | + } |
| 1264 | + } |
| 1265 | + } |
| 1266 | + } |
| 1267 | + } |
| 1268 | + |
| 1269 | + private void resumeAudioPlayoutInternal() { |
| 1270 | + isAudioPlayoutPaused = false; |
| 1271 | + |
| 1272 | + if (pausedTrackVolumes.isEmpty()) { |
| 1273 | + return; |
| 1274 | + } |
| 1275 | + |
| 1276 | + Map<String, Double> volumesToRestore = new HashMap<>(pausedTrackVolumes); |
| 1277 | + pausedTrackVolumes.clear(); |
| 1278 | + |
| 1279 | + for (Map.Entry<String, Double> entry : volumesToRestore.entrySet()) { |
| 1280 | + String trackId = entry.getKey(); |
| 1281 | + double targetVolume = entry.getValue(); |
| 1282 | + MediaStreamTrack track = getTrackForId(trackId, null); |
| 1283 | + if (track instanceof AudioTrack) { |
| 1284 | + try { |
| 1285 | + ((AudioTrack) track).setVolume(targetVolume); |
| 1286 | + trackVolumeCache.put(trackId, targetVolume); |
| 1287 | + } catch (Exception e) { |
| 1288 | + Log.e(TAG, "resumeAudioPlayoutInternal: setVolume failed for track " + trackId, e); |
| 1289 | + } |
| 1290 | + } |
| 1291 | + } |
| 1292 | + } |
| 1293 | + |
1225 | 1294 | private List<IceServer> createIceServers(ConstraintsArray iceServersArray) { |
1226 | 1295 | final int size = (iceServersArray == null) ? 0 : iceServersArray.size(); |
1227 | 1296 | List<IceServer> iceServers = new ArrayList<>(size); |
@@ -1781,6 +1850,11 @@ public void mediaStreamTrackSetVolume(final String id, final double volume, Stri |
1781 | 1850 | Log.d(TAG, "setVolume(): " + id + "," + volume); |
1782 | 1851 | try { |
1783 | 1852 | ((AudioTrack) track).setVolume(volume); |
| 1853 | + trackVolumeCache.put(id, volume); |
| 1854 | + if (!pausedTrackVolumes.isEmpty() && pausedTrackVolumes.containsKey(id)) { |
| 1855 | + pausedTrackVolumes.put(id, volume); |
| 1856 | + ((AudioTrack) track).setVolume(0.0); |
| 1857 | + } |
1784 | 1858 | } catch (Exception e) { |
1785 | 1859 | Log.e(TAG, "setVolume(): error", e); |
1786 | 1860 | } |
@@ -2406,6 +2480,35 @@ public void rtpSenderSetStreams(String peerConnectionId, String rtpSenderId, Lis |
2406 | 2480 | } |
2407 | 2481 | } |
2408 | 2482 |
|
| 2483 | + @Override |
| 2484 | + public void onRemoteAudioTrackAdded(AudioTrack track) { |
| 2485 | + if (track == null) { |
| 2486 | + return; |
| 2487 | + } |
| 2488 | + |
| 2489 | + String trackId = track.id(); |
| 2490 | + trackVolumeCache.putIfAbsent(trackId, 1.0); |
| 2491 | + |
| 2492 | + if (isAudioPlayoutPaused) { |
| 2493 | + double previousVolume = trackVolumeCache.getOrDefault(trackId, 1.0); |
| 2494 | + pausedTrackVolumes.put(trackId, previousVolume); |
| 2495 | + try { |
| 2496 | + track.setVolume(0.0); |
| 2497 | + } catch (Exception e) { |
| 2498 | + Log.e(TAG, "onRemoteAudioTrackAdded: setVolume failed for track " + trackId, e); |
| 2499 | + } |
| 2500 | + } |
| 2501 | + } |
| 2502 | + |
| 2503 | + @Override |
| 2504 | + public void onRemoteAudioTrackRemoved(String trackId) { |
| 2505 | + if (trackId == null) { |
| 2506 | + return; |
| 2507 | + } |
| 2508 | + |
| 2509 | + pausedTrackVolumes.remove(trackId); |
| 2510 | + trackVolumeCache.remove(trackId); |
| 2511 | + } |
2409 | 2512 |
|
2410 | 2513 | public void reStartCamera() { |
2411 | 2514 | if (null == getUserMediaImpl) { |
|
0 commit comments