8000 Add dtmf support for web and macos, fix bugs, add dtmf example on loo… · tmthecoder/flutter-webrtc@c69f920 · GitHub
[go: up one dir, main page]

Skip to content

Commit c69f920

Browse files
committed
Add dtmf support for web and macos, fix bugs, add dtmf example on loopback.
1 parent 9adb13d commit c69f920

File tree

9 files changed

+128
-59
lines changed

9 files changed

+128
-59
lines changed

android/src/main/java/com/cloudwebrtc/webrtc/MethodCallHandlerImpl.java

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,28 @@
11
package com.cloudwebrtc.webrtc;
22

3-
import static com.cloudwebrtc.webrtc.utils.MediaConstraintsUtils.parseMediaConstraints;
4-
53
import android.app.Activity;
64
import android.content.Context;
75
import android.graphics.SurfaceTexture;
86
import android.hardware.Camera;
97
import android.hardware.Camera.CameraInfo;
108
import android.util.Log;
119
import android.util.LongSparseArray;
10+
1211
import androidx.annotation.NonNull;
1312
import androidx.annotation.Nullable;
13+
1414
import com.cloudwebrtc.webrtc.record.AudioChannel;
1515
import com.cloudwebrtc.webrtc.record.FrameCapturer;
1616
import com.cloudwebrtc.webrtc.utils.AnyThreadResult;
1717
import com.cloudwebrtc.webrtc.utils.ConstraintsArray;
1818
import com.cloudwebrtc.webrtc.utils.ConstraintsMap;
1919
import com.cloudwebrtc.webrtc.utils.EglUtils;
2020
import com.cloudwebrtc.webrtc.utils.ObjectType;
21-
import com.cloudwebrtc.webrtc.utils.RTCAudioManager;
22-
import io.flutter.plugin.common.BinaryMessenger;
23-
import io.flutter.plugin.common.EventChannel;
24-
import io.flutter.plugin.common.MethodCall;
25-
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
26-
import io.flutter.plugin.common.MethodChannel.Result;
27-
import io.flutter.view.TextureRegistry;
28-
import io.flutter.view.TextureRegistry.SurfaceTextureEntry;
29-
import java.io.File;
30-
import java.io.UnsupportedEncodingException;
31-
import java.nio.ByteBuffer;
32-
import java.util.ArrayList;
33-
import java.util.HashMap;
34-
import java.util.List;
35-
import java.util.Map;
36-
import java.util.Map.Entry;
37-
import java.util.UUID;
21+
3822
import org.webrtc.AudioTrack;
3923
import org.webrtc.DefaultVideoDecoderFactory;
4024
import org.webrtc.DefaultVideoEncoderFactory;
25+
import org.webrtc.DtmfSender;
4126
import org.webrtc.EglBase;
4227
import org.webrtc.IceCandidate;
4328
import org.webrtc.Logging;
@@ -60,13 +45,34 @@
6045
import org.webrtc.PeerConnectionFactory;
6146
import org.webrtc.PeerConnectionFactory.InitializationOptions;
6247
import org.webrtc.PeerConnectionFactory.Options;
48+
import org.webrtc.RtpSender;
6349
import org.webrtc.SdpObserver;
6450
import org.webrtc.SessionDescription;
6551
import org.webrtc.SessionDescription.Type;
6652
import org.webrtc.VideoTrack;
6753
import org.webrtc.audio.AudioDeviceModule;
6854
import org.webrtc.audio.JavaAudioDeviceModule;
6955

56+
import java.io.File;
57+
import java.io.UnsupportedEncodingException;
58+
import java.nio.ByteBuffer;
59+
import java.util.ArrayList;
60+
import java.util.HashMap;
61+
import java.util.List;
62+
import java.util.Map;
63+
import java.util.Map.Entry;
64+
import java.util.UUID;
65+
66+
import io.flutter.plugin.common.BinaryMessenger;
67+
import io.flutter.plugin.common.EventChannel;
68+
import io.flutter.plugin.common.MethodCall;
69+
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
70+
import io.flutter.plugin.common.MethodChannel.Result;
71+
import io.flutter.view.TextureRegistry;
72+
import io.flutter.view.TextureRegistry.SurfaceTextureEntry;
73+
74+
import static com.cloudwebrtc.webrtc.utils.MediaConstraintsUtils.parseMediaConstraints;
75+
7076
public class MethodCallHandlerImpl implements MethodCallHandler, StateProvider {
7177

7278

example/lib/src/loopback_sample.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ class _MyAppState extends State<LoopBackSample> {
176176
_timer.cancel();
177177
}
178178

179+
void _sendDtmf() async {
180+
var dtmfSender =
181+
_peerConnection.createDtmfSender(_localStream.getAudioTracks()[0]);
182+
await dtmfSender.sendDtmf('123#');
183+
}
184+
179185
@override
180186
Widget build(BuildContext context) {
181187
var widgets = <Widget>[
@@ -189,6 +195,14 @@ class _MyAppState extends State<LoopBackSample> {
189195
return Scaffold(
190196
appBar: AppBar(
191197
title: Text('LoopBack example'),
198+
actions: _inCalling
199+
? <Widget>[
200+
IconButton(
201+
icon: Icon(Icons.keyboard),
202+
onPressed: _sendDtmf,
203+
),
204+
]
205+
: null,
192206
),
193207
body: OrientationBuilder(
194208
builder: (context, orientation) {

ios/Classes/FlutterWebRTCPlugin.m

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult) result
243243
NSDictionary* argsMap = call.arguments;
244244
NSString* peerConnectionId = argsMap[@"peerConnectionId"];
245245
NSString* tone = argsMap[@"tone"];
246-
NSString* durationString = argsMap[@"duration"];
247-
NSString* gapString = argsMap[@"gap"];
246+
int duration = ((NSNumber*)argsMap[@"duration"]).intValue;
247+
int interToneGap = ((NSNumber*)argsMap[@"gap"]).intValue;
248248

249-
double duration = [durationString doubleValue];
250-
double gap = [gapString doubleValue];
251249
RTCPeerConnection *peerConnection = self.peerConnections[peerConnectionId];
252250
if(peerConnection) {
253251

@@ -260,8 +258,10 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult) result
260258
if(audioSender){
261259
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
262260
[queue addOperationWithBlock:^{
261+
double durationMs = duration / 1000.0;
262+
double interToneGapMs = interToneGap / 1000.0;
263263
[audioSender.dtmfSender insertDtmf :(NSString *)tone
264-
duration:(NSTimeInterval) duration interToneGap:(NSTimeInterval)gap];
264+
duration:(NSTimeInterval) durationMs interToneGap:(NSTimeInterval)interToneGapMs];
265265
NSLog(@"DTMF Tone played ");
266266
}];
267267
}

lib/flutter_webrtc.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export 'src/media_stream_track.dart'
1111
if (dart.library.html) 'src/web/media_stream_track.dart';
1212
export 'src/rtc_data_channel.dart'
1313
if (dart.library.html) 'src/web/rtc_data_channel.dart';
14+
export 'src/rtc_dtmf_sender.dart';
1415
export 'src/rtc_ice_candidate.dart'
1516
if (dart.library.html) 'src/web/rtc_ice_candidate.dart';
1617
export 'src/rtc_peerconnection.dart'
@@ -23,4 +24,3 @@ export 'src/rtc_stats_report.dart';
2324
export 'src/rtc_video_view.dart'
2425
if (dart.library.html) 'src/web/rtc_video_view.dart';
2526
export 'src/utils.dart' if (dart.library.html) 'src/web/utils.dart';
26-
export 'rtc_dtmf_sender.dart';

lib/src/rtc_dtmf_sender.dart

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,30 @@
1-
import 'dart:io';
2-
import 'webrtc.dart';
31
import 'package:flutter/services.dart';
42

3+
import 'utils.dart';
4+
55
class RTCDTMFSender {
6+
RTCDTMFSender(this._peerConnectionId);
67
// peer connection Id must be defined as a variable where this function will be called.
78
final String _peerConnectionId;
9+
final MethodChannel _channel = WebRTC.methodChannel();
810

9-
RTCDTMFSender(this._peerConnectionId);
10-
11-
Future<void> sendDtmf(String tone, {var duration, var gap}) async {
12-
final MethodChannel _channel = WebRTC.methodChannel();
13-
14-
var _duration = Platform.isIOS ? 0.5 : 500;
15-
var _gap = Platform.isIOS ? 0.05 : 50;
16-
// IOS accepts gap and duration in seconds so conversion to be needed
17-
if (duration != null) {
18-
if (Platform.isIOS) {
19-
_duration = duration / 1000;
20-
} else {
21-
_duration = duration;
22-
}
23-
}
24-
25-
if (gap != null) {
26-
if (Platform.isIOS) {
27-
_gap = gap / 1000;
28-
} else {
29-
_gap = gap;
30-
}
31-
}
32-
11+
/// tones:A String containing the DTMF codes to be transmitted to the recipient.
12+
/// Specifying an empty string as the tones parameter clears the tone
13+
/// buffer, aborting any currently queued tones. A "," character inserts
14+
/// a two second delay.
15+
/// duration: This value must be between 40 ms and 6000 ms (6 seconds).
16+
/// The default is 100 ms.
17+
/// interToneGap: The length of time, in milliseconds, to wait between tones.
18+
/// The browser will enforce a minimum value of 30 ms (that is,
19+
/// if you specify a lower value, 30 ms will be used instead);
20+
/// the default is 70 ms.
21+
Future<void> sendDtmf(String tones,
22+
{int duration = 100, int interToneGap = 70}) async {
3323
await _channel.invokeMethod('sendDtmf', <String, dynamic>{
3424
'peerConnectionId': _peerConnectionId,
35-
'tone': tone,
36-
'duration': _duration,
37-
'gap': _gap,
25+
'tone': tones,
26+
'duration': duration,
27+
'gap': interToneGap,
3828
F438 });
3929
}
4030
}

lib/src/rtc_peerconnection.dart

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ class RTCPeerConnection {
3636
_eventSubscription = _eventChannelFor(_peerConnectionId)
3737
.receiveBroadcastStream()
3838
.listen(eventListener, onError: errorListener);
39-
40-
_dtmfSender = RTCDTMFSender(_peerConnectionId);
4139
}
4240

4341
// private:
@@ -51,7 +49,6 @@ class RTCPeerConnection {
5149
RTCSignalingState _signalingState;
5250
RTCIceGatheringState _iceGatheringState;
5351
RTCIceConnectionState _iceConnectionState;
54-
RTCDTMFSender _dtmfSender;
5552
// public: delegate
5653
SignalingStateCallback onSignalingState;
5754
IceGatheringStateCallback onIceGatheringState;
@@ -78,8 +75,6 @@ class RTCPeerConnection {
7875

7976
RTCIceConnectionState get iceConnectionState => _iceConnectionState;
8077

81-
RTCDTMFSender get dtmfSender => _dtmfSender;
82-
8378
/*
8479
* PeerConnection event listener.
8580
*/
@@ -362,6 +357,10 @@ class RTCPeerConnection {
362357
}
363358
}
364359

360+
RTCDTMFSender createDtmfSender(MediaStreamTrack track) {
361+
return RTCDTMFSender(_peerConnectionId);
362+
}
363+
365364
Future<Null> close() async {
366365
try {
367366
await _channel.invokeMethod('peerConnectionClose', <String, dynamic>{

lib/src/web/rtc_dtmf_sender.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'dart:html' as html;
2+
3+
class RTCDTMFSender {
4+
RTCDTMFSender(this._jsDtmfSender);
5+
final html.RtcDtmfSender _jsDtmfSender;
6+
7+
/// tones:A String containing the DTMF codes to be transmitted to the recipient.
8+
/// Specifying an empty string as the tones parameter clears the tone
9+
/// buffer, aborting any currently queued tones. A "," character inserts
10+
/// a two second delay.
11+
/// duration: This value must be between 40 ms and 6000 ms (6 seconds).
12+
/// The default is 100 ms.
13+
/// interToneGap: The length of time, in milliseconds, to wait between tones.
14+
/// The browser will enforce a minimum value of 30 ms (that is,
15+
/// if you specify a lower value, 30 ms will be used instead);
16+
/// the default is 70 ms.
17+
Future<void> sendDtmf(String tones,
18+
{int duration = 100, int interToneGap = 70}) async {
19+
return _jsDtmfSender.insertDtmf(tones, duration, interToneGap);
20+
}
21+
}

lib/src/web/rtc_peerconnection.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import '../rtc_stats_report.dart';
88
import 'media_stream.dart';
99
import 'media_stream_track.dart';
1010
import 'rtc_data_channel.dart';
11+
import 'rtc_dtmf_sender.dart';
1112
import 'rtc_ice_candidate.dart';
1213
import 'rtc_session_description.dart';
1314

@@ -233,4 +234,9 @@ class RTCPeerConnection {
233234
jsutil.callMethod(_jsPc, 'addTransceiver', [type, jsOptions]);
234235
}
235236
}
237+
238+
RTCDTMFSender createDtmfSender(MediaStreamTrack track) {
239+
var jsDtmfSender = _jsPc.createDtmfSender(track.jsTrack);
240+
return RTCDTMFSender(jsDtmfSender);
241+
}
236242
}

macos/Classes/FlutterWebRTCPlugin.m

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,39 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult) result
208208
message:[NSString stringWithFormat:@"Error: peerConnection not found!"]
209209
details:nil]);
210210
}
211+
} else if ([@"sendDtmf" isEqualToString:call.method]) {
212+
NSDictionary* argsMap = call.arguments;
213+
NSString* peerConnectionId = argsMap[@"peerConnectionId"];
214+
NSString* tone = argsMap[@"tone"];
215+
int duration = ((NSNumber*)argsMap[@"duration"]).intValue;
216+
int interToneGap = ((NSNumber*)argsMap[@"gap"]).intValue;
217+
218+
RTCPeerConnection *peerConnection = self.peerConnections[peerConnectionId];
219+
if(peerConnection) {
220+
221+
RTCRtpSender* audioSender = nil ;
222+
for( RTCRtpSender *rtpSender in peerConnection.senders){
223+
if([[[rtpSender track] kind] isEqualToString:@"audio"]) {
224+
audioSender = rtpSender;
225+
}
226+
}
227+
if(audioSender){
228+
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
229+
[queue addOperationWithBlock:^{
230+
double durationMs = duration / 1000.0;
231+
double interToneGapMs = interToneGap / 1000.0;
232+
[audioSender.dtmfSender insertDtmf :(NSString *)tone
233+
duration:(NSTimeInterval) durationMs interToneGap:(NSTimeInterval)interToneGapMs];
234+
NSLog(@"DTMF Tone played ");
235+
}];
236+
}
237+
238+
result(@{@"result": @"success"});
239+
} else {
240+
result([FlutterError errorWithCode:[NSString stringWithFormat:@"%@Failed",call.method]
241+
message:[NSString stringWithFormat:@"Error: peerConnection not found!"]
242+
details:nil]);
243+
}
211244
} else if ([@"addCandidate" isEqualToString:call.method]) {
212245
NSDictionary* argsMap = call.arguments;
213246
NSString* peerConnectionId = argsMap[@"peerConnectionId"];

0 commit comments

Comments
 (0)
0