In the above error, I tried to get the parsedResponse and add handling for the statusCode, but I couldn't catch the error.
The linkException doesn't seem to recognize the parsedResponse. If you know a solution, please let me know.
thank you
Below is the graphql client I made and use using graphql_flutter
import 'package:firebase_auth/firebase_auth.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
// ignore: implementation_imports
import 'package:gql/src/ast/ast.dart';
import 'package:noling_app/config/parser.dart';
class MyGraphQLClient {
late GraphQLClient _client;
GraphQLClient get client => _client;
GraphQLClient setClient(String idToken) {
HttpLink _httpLink = HttpLink(
'http://localhost:5005/noling-develop/asia-northeast3/graphql',
parser: CustomResponseParser(),
defaultHeaders: {
'X-USER-TOKEN': idToken,
},
);
Link _link;
if (idToken != '' && idToken.isNotEmpty) {
final AuthLink authLink = AuthLink(
getToken: () => idToken,
headerKey: 'X-USER-TOKEN',
);
_link = authLink.concat(_httpLink);
} else {
_link = _httpLink;
}
_client = GraphQLClient(
cache: GraphQLCache(),
link: _link,
);
return _client;
}
Future<dynamic> query(
DocumentNode document, {
Map<String, dynamic>? data,
}) async {
try {
QueryResult result = await _client.query(QueryOptions(
document: document,
variables: data ?? {},
));
if (result.hasException) {
print(result);
var message = result.exception!.graphqlErrors.first.message;
throw Exception(message);
}
return result.data;
} catch (e) {
print("error catch ?");
rethrow;
}
}
Future<dynamic> mutate(
DocumentNode document, {
Map<String, dynamic>? data,
}) async {
var result = await _client.mutate(MutationOptions(
document: document,
variables: data ?? {},
));
if (result.hasException) {
var message = result.exception!.graphqlErrors.first.message;
throw GraphQLError(message: message);
}
return result.data;
}
}
MyGraphQLClient graphQLClient = MyGraphQLClient();
In the MyGraphQLClient class, I created and used a CustomResponseParser like the one I saw in another issue post, but to no avail.
class CustomResponseParser extends ResponseParser {
#override
Response parseResponse(Map<String, dynamic> body) {
Map<String, String> errors = new Map();
if (body["errors"] != null) {
errors['message'] = body["errors"][0];
}
Response res = Response(
response: body,
errors: (body["errors"] as List?)
?.map(
(dynamic error) => parseError(errors),
)
.toList(),
data: body["data"] as Map<String, dynamic>?,
context: const Context().withEntry(
ResponseExtensions(
body["extensions"],
),
),
);
print(res);
return res;
}
#override
GraphQLError parseError(Map<String, dynamic> error) {
return GraphQLError(
message: error['message'],
);
}
}
Related
I have a flutter application that uses graphql: ^5.0.0 to perform mutations and queries on my database and I'm trying to handle invalid token exceptions I get. When I get an invalid token error from my server, an error is thrown here.
Here is the error making its way up into my code
here is my code:
try {
final QueryResult result = await client.query(options);
List<dynamic> taskList = result.data!['userTasksConnections']['tasks'];
List<Task> tasks = [];
for(int i = 0; i < taskList.length; i++) {
tasks.add(Task.fromJson(taskList[i]));
}
return tasks;
} on HttpLinkServerException catch(e) {
if(e.parsedResponse?.errors?[0] == 'Invalid Token'){
await UserRepo().getAccessToken();
return getTasks(page: page, keyword: keyword);
}
else{
return [];
}
}
since the error is clearly of type HttpLinkServerException I have an on HttpLinkServerException catch(). However, when the code runs the exception is not caught in the catch block and the code continues after the result await as if nothing happened, causing a null data exception on this line
List<dynamic> taskList = result.data!['userTasksConnections']['tasks'];
You need to write your own parser to fix this issue. You do so with something like this
import 'package:graphql/client.dart';
class CustomResponseParser extends ResponseParser {
#override
Response parseResponse(Map<String, dynamic> body) {
Map<String, String> errors = new Map();
if(body["errors"] != null) {
errors['message'] = body["errors"][0];
}
Response res = Response(
errors: (body["errors"] as List?)
?.map(
(dynamic error) => parseError(errors),
)
.toList(),
data: body["data"] as Map<String, dynamic>?,
context: Context().withEntry(
ResponseExtensions(
body["extensions"],
),
),
);
return res;
}
#override
GraphQLError parseError(Map<String, dynamic> error) {
return GraphQLError(
message: error['message'],
);
}
}
And then you use it when initializing your graphqlClient like this
final GraphQLClient client = GraphQLClient(
cache: GraphQLCache(),
link: AuthLink(getToken: () {
if (store.state.auth.accessToken == '') {
return "";
} else {
return "Bearer ${store.state.auth.accessToken}";
}
}).concat(
HttpLink(
Environment().config.apiHost,
parser: CustomResponseParser()
)
)
);
Now I try to transform my Andorid project to flutter. but I stucked on an api call.
here is my android code in Kotlin:
/**
* sendSms
*
* #return
*/
#Headers("Content-Type: application/json;charset=UTF-8")
#POST("uaa/sms/send/code")
fun sendSms(#Body params: Map<String, String?>): Observable<ApiResult<String>>
Now I want to implement this api call in flutter use dio, but I still got wrong, my flutter code
is :
class Req {
static Req _instance;
static const int connectTimeOut = 5 * 1000;
static const int receiveTimeOut = 7 * 1000;
static Req getInstance() {
if (_instance == null) {
_instance = Req._internal();
}
return _instance;
}
Dio _client;
Req._internal() {
if (_client == null) {
BaseOptions options = new BaseOptions();
options.connectTimeout = connectTimeOut;
options.receiveTimeout = receiveTimeOut;
_client = new Dio(BaseOptions(
baseUrl: 'https://gw.ec.iunicorn.com/',
));
// 添加缓存插件
_client.interceptors.add(Global.netCache);
//添加token
_client.interceptors.add(Global.tokenInterceptor);
_client.interceptors.add(Global.logInterceptor);
// dio.options.headers[HttpHeaders.authorizationHeader] = Global.profile.token;
_client.options.headers['source'] = 'ANDROID';
if (!Global.isRelease) {
(_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
// client.findProxy = (uri) {
// return "PROXY 10.1.10.250:8888";
// };
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
};
}
}
}
//post请求
void post(
String url,
OnData callBack, {
Map<String, String> params,
Options options,
FormData formData,
OnError errorCallBack,
CancelToken token,
}) async {
this._request(
url,
callBack,
method: RequestType.POST,
options: options,
formData: formData,
params: params,
errorCallBack: errorCallBack,
token: token,
);
}
void _request(
String url,
OnData callBack, {
RequestType method,
Map<String, String> params,
Options options,
FormData formData,
OnError errorCallBack,
ProgressCallback progressCallBack,
CancelToken token,
}) async {
final id = _id++;
int statusCode;
try {
Response response;
if (method == RequestType.GET) {
if (mapNoEmpty(params)) {
response = await _client.get(url,
queryParameters: params, cancelToken: token);
} else {
response = await _client.get(url, cancelToken: token);
}
} else {
if (mapNoEmpty(params) || formData != null) {
response = await _client.post(
url,
data: formData ?? params,
onSendProgress: progressCallBack,
cancelToken: token,
);
} else {
response = await _client.post(url, cancelToken: token);
}
}
statusCode = response.statusCode;
if (response != null) {
if (response.data is List) {
Map data = response.data[0];
callBack(data);
} else {
Map data = response.data;
callBack(data);
}
print('HTTP_REQUEST_URL::[$id]::$url');
print('HTTP_REQUEST_BODY::[$id]::${params ?? ' no'}');
print('HTTP_RESPONSE_BODY::[$id]::${response.data}');
}
if (statusCode < 0) {
_handError(errorCallBack, statusCode);
return;
}
} catch (e) {
_handError(errorCallBack, statusCode);
}
}
///处理异常
static void _handError(OnError errorCallback, int statusCode) {
String errorMsg = 'Network request error';
if (errorCallback != null) {
errorCallback(errorMsg, statusCode);
}
print("HTTP_RESPONSE_ERROR::$errorMsg code:$statusCode");
}
}
void sendSms(BuildContext context, Callback callback) async {
Req.getInstance().post(
ApiPath.SEND_SMS,
(t) {
SmsResponse r = SmsResponse.fromJson(t);
print(r);
if (callback != null) {
callback();
}
},
formData: FormData.fromMap({
'phoneNumber':'182********'
}),
options: RequestOptions(
headers: {
HttpHeaders.contentTypeHeader: 'application/json;charset=UTF-8',
}),
errorCallBack: (msg, code) {
Fluttertoast.showToast(
msg: AppLocalizations.of(context).send_sms_fail,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.orangeAccent,
timeInSecForIosWeb: 1);
});
}
Now I want to know is the data in dio is equivalent to the #Body in java retrofit, if not, how can I do?
Doing this in plain Dio leads to a lot of boilerplate. There is also an equivalent retrofit package for Flutter inspired by the same package for Android. https://pub.dev/packages?q=retrofit
From there it's almost the same, you just add () after #Body. Here is an example
#POST('/auth/change-password')
Future<bool> changePassword({
#required #Body() Map<String, dynamic> params,
#required #Header('auth-token') String token,
});
i want to return json object list but i dont know how
i'm using the sample doc from flutter the
here is my code
Future<Album> fetchAlbum() async {
final response =
await http.get('https://vpic.nhtsa.dot.gov/api/vehicles/getmodelsformake/honda?format=json');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class Album {
final String userId;
final List <String> Cm;
Album({this.userId, this.Cm});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['Results'][0]['Make_Name'],
Cm: for( var i = 0 ; i < json['Count']; i++ ) {
Cm.add(json['Results'][i]['Make_Name']);
}
);
}
}
the error in Cm: for... line
In your code snippet you did not created a class to refer Results list. Try bellow code snippet.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response = await http.get(
'https://vpic.nhtsa.dot.gov/api/vehicles/getmodelsformake/honda?format=json');
if (response.statusCode == 200) {
return Album.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load album');
}
}
class Album {
int count;
String message;
String searchCriteria;
List<Results> results;
Album({this.count, this.message, this.searchCriteria, this.results});
Album.fromJson(Map<String, dynamic> json) {
count = json['Count'];
message = json['Message'];
searchCriteria = json['SearchCriteria'];
if (json['Results'] != null) {
results = new List<Results>();
json['Results'].forEach((v) {
results.add(new Results.fromJson(v));
});
}
}
}
class Results {
int makeID;
String makeName;
int modelID;
String modelName;
Results({this.makeID, this.makeName, this.modelID, this.modelName});
Results.fromJson(Map<String, dynamic> json) {
makeID = json['Make_ID'];
makeName = json['Make_Name'];
modelID = json['Model_ID'];
modelName = json['Model_Name'];
}
}
As the for-loop is not returning the list to the cm field, you may try using .map to do the mapping and return it.
Cm: json['Results'].map((e)=>e['Make_Name']).toList()
First off, Flutter is a Framework for Dart language, so you don't need Flutter to run that code. Run code below on console:
import 'dart:convert';
import 'package:http/http.dart' as http;
class NetService {
static Future fetchJsonData(String url) {
return
http.get(url)
.then((response) => response?.statusCode == 200 ? jsonDecode(response.body) : null)
.catchError((err) => print(err));
}
static Future<void> fetchCarModels() {
return
fetchJsonData('https://vpic.nhtsa.dot.gov/api/vehicles/getmodelsformake/honda?format=json')
.then((response) {
if (response != null) {
final Map<String, dynamic> data = response;
print('''
Count : ${data["Count"]}
Message : ${data["Message"]}
Search Criteria : ${data["SearchCriteria"]}
Models :
${(data["Results"] as List)?.fold<String>("", (m, e) => m + (e as Map<String, dynamic>)["Model_Name"] + ", ")}
'''
);
}
})
.catchError((err) => print(err));
}
}
void main(List<String> arguments) async {
await NetService.fetchCarModels();
}
So I'm building a video calling application using flutter, flutterWeb, and the WebRTC package.
I have a spring boot server sitting in the middle to pass the messages between the two clients.
Each side shows the local video, but neither shows the remote. Audio does work though. I got som nasty feedback loops. Testing with headphones showed that audio does indeed work.
My singaling code
typedef void StreamStateCallback(MediaStream stream);
class CallingService {
String sendToUserId;
String currentUserId;
final String authToken;
final StompClient _client;
final StreamStateCallback onAddRemoteStream;
final StreamStateCallback onRemoveRemoteStream;
final StreamStateCallback onAddLocalStream;
RTCPeerConnection _peerConnection;
List<RTCIceCandidate> _remoteCandidates = [];
String destination;
var hasOffer = false;
var isNegotiating = false;
MediaStream _localStream;
final Map<String, dynamic> _constraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true,
},
'optional': [],
};
CallingService(
this._client,
this.sendToUserId,
this.currentUserId,
this.authToken,
this.onAddRemoteStream,
this.onRemoveRemoteStream,
this.onAddLocalStream) {
destination = '/app/start-call/$sendToUserId';
print("destination $destination");
_client.subscribe(
destination: destination,
headers: {'Authorization': "$authToken"},
callback: (StompFrame frame) => processMessage(jsonDecode(frame.body)));
}
Future<void> startCall() async {
await processRemoteStream();
RTCSessionDescription description =
await _peerConnection.createOffer(_constraints);
await _peerConnection.setLocalDescription(description);
var message = RtcMessage(RtcMessageType.OFFER, currentUserId, {
'description': {'sdp': description.sdp, 'type': description.type},
});
sendMessage(message);
}
Future<void> processMessage(Map<String, dynamic> messageJson) async {
var message = RtcMessage.fromJson(messageJson);
if (message.from == currentUserId) {
return;
}
print("processing ${message.messageType.toString()}");
switch (message.messageType) {
case RtcMessageType.BYE:
// TODO: Handle this case.
break;
case RtcMessageType.LEAVE:
// TODO: Handle this case.
break;
case RtcMessageType.CANDIDATE:
await processCandidate(message);
break;
case RtcMessageType.ANSWER:
await processAnswer(message);
break;
case RtcMessageType.OFFER:
await processOffer(message);
break;
}
}
Future<void> processCandidate(RtcMessage candidate) async {
Map<String, dynamic> map = candidate.data['candidate'];
var rtcCandidate = RTCIceCandidate(
map['candidate'],
map['sdpMid'],
map['sdpMLineIndex'],
);
if (_peerConnection != null) {
_peerConnection.addCandidate(rtcCandidate);
} else {
_remoteCandidates.add(rtcCandidate);
}
}
Future<void> processAnswer(RtcMessage answer) async {
if (isNegotiating) {
return;
}
isNegotiating = true;
var description = answer.data['description'];
if (_peerConnection == null) {
return;
}
await _peerConnection.setRemoteDescription(
RTCSessionDescription(description['sdp'], description['type']));
}
Future<void> processOffer(RtcMessage offer) async {
await processRemoteStream();
var description = offer.data['description'];
await _peerConnection.setRemoteDescription(
new RTCSessionDescription(description['sdp'], description['type']));
var answerDescription = await _peerConnection.createAnswer(_constraints);
await _peerConnection.setLocalDescription(answerDescription);
var answerMessage = RtcMessage(RtcMessageType.ANSWER, currentUserId, {
'description': {
'sdp': answerDescription.sdp,
'type': answerDescription.type
},
});
sendMessage(answerMessage);
if (_remoteCandidates.isNotEmpty) {
_remoteCandidates
.forEach((candidate) => _peerConnection.addCandidate(candidate));
_remoteCandidates.clear();
}
}
Future<void> processRemoteStream() async {
_localStream = await createStream();
_peerConnection = await createPeerConnection(_iceServers, _config);
_peerConnection.addStream(_localStream);
_peerConnection.onSignalingState = (state) {
//isNegotiating = state != RTCSignalingState.RTCSignalingStateStable;
};
_peerConnection.onAddStream = (MediaStream stream) {
this.onAddRemoteStream(stream);
};
_peerConnection.onRemoveStream =
(MediaStream stream) => this.onRemoveRemoteStream(stream);
_peerConnection.onIceCandidate = (RTCIceCandidate candidate) {
var data = {
'candidate': {
'sdpMLineIndex': candidate.sdpMlineIndex,
'sdpMid': candidate.sdpMid,
'candidate': candidate.candidate,
},
};
var message = RtcMessage(RtcMessageType.CANDIDATE, currentUserId, data);
sendMessage(message);
};
}
void sendMessage(RtcMessage message) {
_client.send(
destination: destination,
headers: {'Authorization': "$authToken"},
body: jsonEncode(message.toJson()));
}
Map<String, dynamic> _iceServers = {
'iceServers': [
{'urls': 'stun:stun.l.google.com:19302'},
/*
* turn server configuration example.
{
'url': 'turn:123.45.67.89:3478',
'username': 'change_to_real_user',
'credential': 'change_to_real_secret'
},
*/
]
};
final Map<String, dynamic> _config = {
'mandatory': {},
'optional': [
{'DtlsSrtpKeyAgreement': true},
],
};
Future<MediaStream> createStream() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': {
'mandatory': {
'minWidth': '640',
'minHeight': '480',
'minFrameRate': '30',
},
'facingMode': 'user',
'optional': [],
}
};
MediaStream stream = await navigator.getUserMedia(mediaConstraints);
if (this.onAddLocalStream != null) {
this.onAddLocalStream(stream);
}
return stream;
}
}
Here are my widgets
class _CallScreenState extends State<CallScreen> {
StompClient _client;
CallingService _callingService;
RTCVideoRenderer _localRenderer = new RTCVideoRenderer();
RTCVideoRenderer _remoteRenderer = new RTCVideoRenderer();
final UserService userService = GetIt.instance.get<UserService>();
void onConnectCallback(StompClient client, StompFrame connectFrame) async {
var currentUser = await userService.getCurrentUser();
_callingService = CallingService(
_client,
widget.intent.toUserId.toString(),
currentUser.id.toString(),
widget.intent.authToken,
onAddRemoteStream,
onRemoveRemoteStream,
onAddLocalStream);
if (widget.intent.initialMessage != null) {
_callingService.processMessage(jsonDecode(widget.intent.initialMessage));
} else {
_callingService.startCall();
}
}
void onAddRemoteStream(MediaStream stream) {
_remoteRenderer.srcObject = stream;
}
void onRemoveRemoteStream(MediaStream steam) {
_remoteRenderer.srcObject = null;
}
void onAddLocalStream(MediaStream stream) {
_localRenderer.srcObject = stream;
}
#override
void initState() {
super.initState();
_localRenderer.initialize();
_remoteRenderer.initialize();
_client = StompClient(
config: StompConfig(
url: 'ws://${DomainService.getDomainBase()}/stomp',
onConnect: onConnectCallback,
onWebSocketError: (dynamic error) => print(error.toString()),
stompConnectHeaders: {'Authorization': "${widget.intent.authToken}"},
onDisconnect: (message) => print("disconnected ${message.body}"),),
);
_client.activate();
}
#override
Widget build(BuildContext context) {
return PlatformScaffold(
pageTitle: "",
child: Flex(
direction: Axis.vertical,
children: [
Flexible(
flex: 1,
child: RTCVideoView(_localRenderer),
),
Flexible(
flex: 1,
child: RTCVideoView(_remoteRenderer),
)
],
),
);
}
}
I put a print statment in the the widget on the addRemoteStream callback, and it's getting called. So some kind of stream is being sent. I'm just not sure why the video isnt' showing.
So my problem was that I wasn't adding queued candidates to the caller.
I added
sendMessage(answerMessage);
if (_remoteCandidates.isNotEmpty) {
_remoteCandidates
.forEach((candidate) => _peerConnection.addCandidate(candidate));
_remoteCandidates.clear();
}
to the processAnswer method and it works just fine!
So I'm using flutter (And flutter for web) to build a WebRTC client. I have a spring-boot server acting as the go-between for two clients. They both subscribe to a WebSocket to get messages from the other. It does nothing more than that.
I'm getting Error: InvalidStateError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: kStable
I don't know why this error is happening.
Here's the code for the signalling
typedef void StreamStateCallback(MediaStream stream);
class CallingService {
String sendToUserId;
String currentUserId;
final String authToken;
final StompClient _client;
final StreamStateCallback onAddRemoteStream;
final StreamStateCallback onRemoveRemoteStream;
final StreamStateCallback onAddLocalStream;
RTCPeerConnection _peerConnection;
List<RTCIceCandidate> _remoteCandidates = [];
String destination;
var hasOffer = false;
var isNegotiating = false;
final Map<String, dynamic> _constraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true,
},
'optional': [],
};
CallingService(
this._client,
this.sendToUserId,
this.currentUserId,
this.authToken,
this.onAddRemoteStream,
this.onRemoveRemoteStream,
this.onAddLocalStream) {
destination = '/app/start-call/$sendToUserId';
print("destination $destination");
_client.subscribe(
destination: destination,
headers: {'Authorization': "$authToken"},
callback: (StompFrame frame) => processMessage(jsonDecode(frame.body)));
}
Future<void> startCall() async {
await processRemoteStream();
RTCSessionDescription description =
await _peerConnection.createOffer(_constraints);
await _peerConnection.setLocalDescription(description);
var message = RtcMessage(RtcMessageType.OFFER, currentUserId, {
'description': {'sdp': description.sdp, 'type': description.type},
});
sendMessage(message);
}
Future<void> processMessage(Map<String, dynamic> messageJson) async {
var message = RtcMessage.fromJson(messageJson);
if (message.from == currentUserId) {
return;
}
print("processing ${message.messageType.toString()}");
switch (message.messageType) {
case RtcMessageType.BYE:
// TODO: Handle this case.
break;
case RtcMessageType.LEAVE:
// TODO: Handle this case.
break;
case RtcMessageType.CANDIDATE:
await processCandidate(message);
break;
case RtcMessageType.ANSWER:
await processAnswer(message);
break;
case RtcMessageType.OFFER:
await processOffer(message);
break;
}
}
Future<void> processCandidate(RtcMessage candidate) async {
Map<String, dynamic> map = candidate.data['candidate'];
var rtcCandidate = RTCIceCandidate(
map['candidate'],
map['sdpMid'],
map['sdpMLineIndex'],
);
if (_peerConnection != null) {
_peerConnection.addCandidate(rtcCandidate);
} else {
_remoteCandidates.add(rtcCandidate);
}
}
Future<void> processAnswer(RtcMessage answer) async {
if (isNegotiating){
return;
}
var description = answer.data['description'];
await _peerConnection.setRemoteDescription(
RTCSessionDescription(description['sdp'], description['type']));
}
Future<void> processOffer(RtcMessage offer) async {
await processRemoteStream();
var description = offer.data['description'];
await _peerConnection.setRemoteDescription(
new RTCSessionDescription(description['sdp'], description['type']));
var answerDescription = await _peerConnection.createAnswer(_constraints);
await _peerConnection.setLocalDescription(answerDescription);
var answerMessage = RtcMessage(RtcMessageType.ANSWER, currentUserId, {
'description': {
'sdp': answerDescription.sdp,
'type': answerDescription.type
},
});
sendMessage(answerMessage);
if (_remoteCandidates.isNotEmpty) {
_remoteCandidates
.forEach((candidate) => _peerConnection.addCandidate(candidate));
_remoteCandidates.clear();
}
}
Future<void> processRemoteStream() async {
await createStream();
_peerConnection = await createPeerConnection(_iceServers, _config);
_peerConnection.onSignalingState = (state) {
isNegotiating = state != RTCSignalingState.RTCSignalingStateStable;
};
_peerConnection.onAddTrack = (MediaStream stream, _) {
this.onAddRemoteStream(stream);
print("sending stream from track");
};
_peerConnection.onAddStream = (MediaStream stream) {
this.onAddRemoteStream(stream);
print("sending stream");
};
_peerConnection.onRemoveStream =
(MediaStream stream) => this.onRemoveRemoteStream(stream);
_peerConnection.onIceCandidate = (RTCIceCandidate candidate) {
print("sending candidate");
var data = {
'candidate': {
'sdpMLineIndex': candidate.sdpMlineIndex,
'sdpMid': candidate.sdpMid,
'candidate': candidate.candidate,
},
};
var message = RtcMessage(RtcMessageType.CANDIDATE, currentUserId, data);
sendMessage(message);
};
}
void sendMessage(RtcMessage message) {
_client.send(
destination: destination,
headers: {'Authorization': "$authToken"},
body: jsonEncode(message.toJson()));
}
Map<String, dynamic> _iceServers = {
'iceServers': [
{"url" : "stun:stun2.1.google.com:19302"},
{'url' : 'stun:stun.l.google.com:19302'},
/*
* turn server configuration example.
{
'url': 'turn:123.45.67.89:3478',
'username': 'change_to_real_user',
'credential': 'change_to_real_secret'
},
*/
]
};
final Map<String, dynamic> _config = {
'mandatory': {},
'optional': [
{'DtlsSrtpKeyAgreement': true},
],
};
Future<MediaStream> createStream() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': {
'mandatory': {
'minWidth':
'640', // Provide your own width, height and frame rate here
'minHeight': '480',
'minFrameRate': '30',
},
'facingMode': 'user',
'optional': [],
}
};
MediaStream stream = await navigator.getUserMedia(mediaConstraints);
if (this.onAddLocalStream != null) {
this.onAddLocalStream(stream);
}
return stream;
}
}
My first problem was I was not setting the local description for the offer/answer stage.
However, when I add a new stun server, I get the same exception. Either way, I don't get a remote stream showing.
So when I was creating the offer and answer I wasn't setting local description. So there's that.
It's still not showing remote connections though.