I had done with calling peer to peer in flutter but there is a problem how can I enable ear speaker instead of the loudspeaker in a flutter. Please help me to do this,
while establishing calling from one device to another device it only enables the loudspeaker instead of ear speaker. Thanks in advance.
Future<void> joinRoom(String roomId, RTCVideoRenderer remoteVideo) async {
FirebaseFirestore db = FirebaseFirestore.instance;
DocumentReference roomRef = db.collection('rooms').doc('$roomId');
var roomSnapshot = await roomRef.get();
log('Got room ${roomSnapshot.exists}');
if (roomSnapshot.exists) {
log('Create PeerConnection with configuration: $configuration');
peerConnection = await createPeerConnection(configuration);
registerPeerConnectionListeners();
localStream?.getTracks()?.forEach((track) {
peerConnection?.addTrack(track, localStream);
});
// Code for collecting ICE candidates below
var calleeCandidatesCollection = roomRef.collection('calleeCandidates');
peerConnection.onIceCandidate = (RTCIceCandidate candidate) {
if (candidate == null) {
log('onIceCandidate: complete!');
return;
}
log('onIceCandidate: ${candidate.toMap()}');
calleeCandidatesCollection.add(candidate.toMap());
};
// Code for collecting ICE candidate above
peerConnection?.onTrack = (RTCTrackEvent event) {
log('Got remote track: ${event.streams[0]}');
event.streams[0].getTracks().forEach((track) {
log('Add a track to the remoteStream: $track');
remoteStream?.addTrack(track);
});
};
// Code for creating SDP answer below
var data = roomSnapshot.data();
log('Got offer $data');
var offer = data['offer'];
await peerConnection?.setRemoteDescription(
RTCSessionDescription(offer['sdp'], offer['type']),
);
var answer = await peerConnection.createAnswer();
log('Created Answer $answer');
await peerConnection.setLocalDescription(answer);
Map<String, dynamic> roomWithAnswer = {
'answer': {'type': answer.type, 'sdp': answer.sdp}
};
await roomRef.update(roomWithAnswer);
// Finished creating SDP answer
// Listening for remote ICE candidates below
roomRef.collection('callerCandidates').snapshots().listen((snapshot) {
snapshot.docChanges.forEach((document) {
var data = document.doc.data();
// log(data);
log('Got new remote ICE candidate: $data');
peerConnection.addCandidate(
RTCIceCandidate(
data['candidate'],
data['sdpMid'],
data['sdpMLineIndex'],
),
);
});
});
}
}
Future<void> openUserMedia(RTCVideoRenderer localVideo,
RTCVideoRenderer remoteVideo, BuildContext contextt) async {
context = contextt;
chatProvider = Provider.of<ChatProvider>(context, listen: false);
var stream = await navigator.mediaDevices.getUserMedia({'audio': true});
localVideo.srcObject = stream;
localStream = stream;
remoteVideo.srcObject = await createLocalMediaStream('key');
}
Related
To upload files to youtube How to do it, I have been studying for about a while.
class YoutubeService {
static Future<YouTubeApi> getYoutubeApi() async {
final GoogleSignIn googleSignIn = GoogleSignIn(
scopes: <String>[
YouTubeApi.youtubeReadonlyScope,
YouTubeApi.youtubeUploadScope
],
);
await googleSignIn.signIn();
// final GoogleSignInAccount? googleSignInAccount = await googleSignIn.signIn();
var httpClient = await googleSignIn.authenticatedClient();
if (httpClient == null) {
print("You didn't allow to proceed with YouTube access");
}
return YouTubeApi(httpClient!);
}
static Future<VideoListResponse> listYoutubePlaylists() async {
var youTubeApi = await getYoutubeApi();
var data = await youTubeApi.videos.list(
['snippet'],
chart: "mostPopular",
);
return data;
}
static File? f;
static Future<Video>? videoInsertRequest;
static Future<Video> upload() async {
var youTubeApi = await getYoutubeApi();
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
f = File(result.files.single.path.toString());
}
//File f = File('assets/sample-mp4-file-small.mp4');
Stream<List<int>> stream = f!.openRead();
Media m = Media(stream, (await f!.length()));
Video video = Video(
snippet: VideoSnippet(
title: 'Test Video',
description: 'Test Upload for My App',
categoryId: '22',
),
);
videoInsertRequest = youTubeApi.videos.insert(
video,
['snippet', 'status'],
uploadMedia: m,
);
videoInsertRequest!.whenComplete(
() => print("Succeed")
);
return videoInsertRequest!;
}
}
if function getYoutubeApi().How do I enter the desired code?
Another question of this function is if login app with google, is this function call if not set? the desired ID
I call upload() function but not upload to youtube. which has been waiting for a very long time because there is no print Succeed value. I am going to develop a progress bar after print Succeed succeeds.
I am developing a streaming application: one application (server) broadcasts a video, and another application (client) displays this video. I used the flutter_webrtc package for real-time communication. I followed the following tutroial:
https://www.youtube.com/watch?v=hAKQzNQmNe0
https://github.com/md-weber/webrtc_tutorial/
Currently, the server application can successfully create a channel and broadcast the video, and the client application can join this channel and watch the video. But when the client leaves the channel and later tries to connect to the same channel, it can't get the video, only a black screen is displayed. There are no errors shown.
I used flutter_riverpod as state management and all codes below are inside StateNotifiers.
Function to create a channel from server application side.
Future<String> startStream(RTCVideoRenderer localVideo) async {
state = ProcessState.loading;
final stream = await navigator.mediaDevices.getUserMedia(<String, dynamic>{
'video': true,
'audio': true,
});
localVideo.srcObject = stream;
localStream = stream;
final roomId = await _createRoom();
await Wakelock.enable();
state = ProcessState.working;
return roomId;
}
Future<String> _createRoom() async {
final db = FirebaseFirestore.instance;
final roomRef = db.collection('rooms').doc();
peerConnection = await createPeerConnection(configuration);
localStream?.getTracks().forEach((track) {
peerConnection?.addTrack(track, localStream!);
});
// Code for collecting ICE candidates below
final callerCandidatesCollection = roomRef.collection('callerCandidates');
peerConnection?.onIceCandidate = (RTCIceCandidate candidate) {
callerCandidatesCollection.add(candidate.toMap() as Map<String, dynamic>);
};
// Add code for creating a room
final offer = await peerConnection!.createOffer();
await peerConnection!.setLocalDescription(offer);
final roomWithOffer = <String, dynamic>{'offer': offer.toMap()};
await roomRef.set(roomWithOffer);
roomId = roomRef.id;
// Listening for remote session description below
roomRef.snapshots().listen((snapshot) async {
final data = snapshot.data();
if (peerConnection?.getRemoteDescription() != null && data != null && data['answer'] != null){
final answer = data['answer'] as Map<String, dynamic>;
final description = RTCSessionDescription(
answer['sdp'] as String?,
answer['type'] as String?,
);
await peerConnection?.setRemoteDescription(description);
}
});
// Listen for remote Ice candidates below
roomRef.collection('calleeCandidates').snapshots().listen((snapshot) {
for (final change in snapshot.docChanges) {
if (change.type == DocumentChangeType.added) {
final data = change.doc.data();
peerConnection!.addCandidate(
RTCIceCandidate(
data?['candidate'] as String?,
data?['sdpMid'] as String?,
data?['sdpMLineIndex'] as int?,
),
);
}
}
});
return roomId!;
}
Function to join a channel from client application side.
Future<bool> startStream(String? roomId, RTCVideoRenderer remoteVideo) async {
if (roomId == null || roomId.isEmpty) {
return false;
}
state = ProcessState.loading;
final result = await _joinRoom(roomId, remoteVideo);
if (result) {
state = ProcessState.working;
await Wakelock.enable();
} else {
state = ProcessState.notInitialized;
}
return result;
}
Future<bool> _joinRoom(String roomId, RTCVideoRenderer remoteVideo) async {
final db = FirebaseFirestore.instance;
final DocumentReference roomRef = db.collection('rooms').doc(roomId);
final roomSnapshot = await roomRef.get();
if (roomSnapshot.exists) {
peerConnection = await createPeerConnection(configuration);
peerConnection?.onAddStream = (MediaStream stream) {
onAddRemoteStream?.call(stream);
remoteStream = stream;
};
// Code for collecting ICE candidates below
final calleeCandidatesCollection = roomRef.collection('calleeCandidates');
peerConnection?.onIceCandidate = (RTCIceCandidate candidate) {
final candidateMap = candidate.toMap() as Map<String, dynamic>;
calleeCandidatesCollection.add(candidateMap);
};
peerConnection?.onTrack = (RTCTrackEvent event) {
event.streams[0].getTracks().forEach((track) {
remoteStream?.addTrack(track);
});
};
// Code for creating SDP answer below
final data = roomSnapshot.data() as Map<String, dynamic>?;
final offer = data?['offer'] as Map<String, dynamic>?;
await peerConnection?.setRemoteDescription(
RTCSessionDescription(
offer?['sdp'] as String?,
offer?['type'] as String?,
),
);
final answer = await peerConnection!.createAnswer();
await peerConnection!.setLocalDescription(answer);
final roomWithAnswer = <String, dynamic>{
'answer': {
'type': answer.type,
'sdp': answer.sdp,
}
};
await roomRef.update(roomWithAnswer);
// Listening for remote ICE candidates below
roomRef.collection('callerCandidates').snapshots().listen((snapshot) {
for (final document in snapshot.docChanges) {
final data = document.doc.data();
peerConnection!.addCandidate(
RTCIceCandidate(
data?['candidate'] as String?,
data?['sdpMid'] as String?,
data?['sdpMLineIndex'] as int?,
),
);
}
});
this.roomId = roomId;
return true;
}
return false;
}
After some research, I found that my problem is the same as in the question Failed to set remote answer sdp: Called in wrong state: stable. This was caused by the fact that one RTCPeerConnection can only establish one peer-to-peer connection. So I could fix this by creating a new RTCPeerConnection on the server side every time a new client wants to join the channel.
Future<String> _createRoom() async {
final db = FirebaseFirestore.instance;
final roomRef = db.collection('rooms').doc();
await newPeerConnection(roomRef);
roomId = roomRef.id;
// Listening for remote session description below
roomRef.snapshots().listen((snapshot) async {
final data = snapshot.data();
if (data != null && data['answer'] != null) {
final answer = data['answer'] as Map<String, dynamic>;
final description = RTCSessionDescription(
answer['sdp'] as String?,
answer['type'] as String?,
);
await peerConnectionList.last.setRemoteDescription(description);
await newPeerConnection(roomRef);
}
});
// Listen for remote Ice candidates below
roomRef.collection('calleeCandidates').snapshots().listen((snapshot) {
for (final change in snapshot.docChanges) {
if (change.type == DocumentChangeType.added) {
final data = change.doc.data();
peerConnectionList.last.addCandidate(
RTCIceCandidate(
data?['candidate'] as String?,
data?['sdpMid'] as String?,
data?['sdpMLineIndex'] as int?,
),
);
}
}
});
return roomId!;
}
Future<void> newPeerConnection(DocumentReference roomRef) async {
final peerConnection = await createPeerConnection(
configuration,
offerSdpConstraints,
);
_registerPeerConnectionListeners(peerConnection);
localStream?.getTracks().forEach((track) {
peerConnection.addTrack(track, localStream!);
});
final offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
final roomWithOffer = <String, dynamic>{'offer': offer.toMap()};
await roomRef.set(roomWithOffer);
peerConnectionList.add(peerConnection);
}
I am buiding an app were I want to run a batch operation in firestore and I want to run it in a different isolate. Here is my code for spawning the isolate:
Future<void> _startAnotherIsolate(String mediaUrl) async {
final isolate = await FlutterIsolate.spawn(isolate1,"hello"); // i need to pass 2 more
arguments
Timer(Duration(seconds: 5), () {
print("Pausing Isolate 1");
isolate.pause();
});
Timer(Duration(seconds: 10), () {
print("Resuming Isolate 1");
isolate.resume();
});
Timer(Duration(seconds: 20), () {
print("Killing Isolate 1");
isolate.kill();
});
}
My code for the isolate:
void isolate1(String data1, String data2) async {
await Firebase.initializeApp();
print("changing profile picture: $phone");
Timer.periodic(Duration(seconds: 1), (timer) => print("Timer Running From Isolate 1"));
var db = FirebaseFirestore.instance;
var batch = db.batch();
FirebaseFirestore.instance.collection("posts").doc(phone).collection("userPosts")
.get().then((querySnapshot) {
for (var document in querySnapshot.docs) {
try {
batch.update(document.reference,{'user_image': mediaUrl});
} on FormatException catch (error) {
// If a document ID is unparsable. Example "lRt931gu83iukSSLwyei" is unparsable.
// print("The document ${error.source} could not be parsed.");
return null;
}
}
return batch.commit();
});
}
I have seen This link and this link but they are not helpful
import 'dart:isolate';
class RequiredArgs {
late final SendPort sendPort;
late int id;
RequiredArgs(this.id, this.sendPort);
}
Future<void> main() async {
ReceivePort receivePort = ReceivePort();
RequiredArgs requiredArgs = RequiredArgs(1122, receivePort.sendPort);
Isolate isolate = await Isolate.spawn(download, requiredArgs);
var resp = await receivePort.first;
print(resp);
}
void download(RequiredArgs requiredArgs) {
final SendPort sendPort = requiredArgs.sendPort;
final id = requiredArgs.id;
print(id);
sendPort.send("yes");
}
We pass the value using the RequiredArgs class. Hope my answer helps.
I create a service to get config from Firebase remote config:
const String _ShowDataBanner = "show_data_banner";
const String _ShowMainBanner = "show_main_banner";
const String _ShowMainColorBanner = "show_main_color_banner";
class RemoteConfigService {
final RemoteConfig _remoteConfig;
final defaults = <String, dynamic>{
_ShowMainBanner: false,
_ShowMainColorBanner: "0xffcccccc"
};
RemoteConfigService({RemoteConfig remoteConfig})
: _remoteConfig = remoteConfig;
static RemoteConfigService _instance;
static Future<RemoteConfigService> getInstance() async {
if (_instance == null) {
_instance = RemoteConfigService(
remoteConfig: await RemoteConfig.instance,
);
}
return _instance;
}
String get showMainBanner => _remoteConfig.getString(_ShowDataBanner);
Future initialise() async {
try {
await _remoteConfig.setDefaults(defaults);
await _fetchAndActivate();
} on FetchThrottledException catch (e) {
print("Remote config fetch throttled: $e");
} catch (e) {
print(
"unable to fetch remote config. Catched or default values will be used");
}
}
Future _fetchAndActivate() async {
// await _remoteConfig.fetch();
await _remoteConfig.fetch(expiration: Duration(seconds: 0));
_remoteConfig
.activateFetched()
.then((value) => print("---------> ${value.toString()}"));
}
}
When i change config from Firebase console I have to stop/start app to updated my config.It is possible to received new config in client from remote immediately when i changed config from console?
Firebase remote config is not meant to be used like this. Its not a real-time thing. But if you want to update immediately after updating config from firebase console, then you can do that by providing expiration parameter a duration of 0 seconds.
await remoteConfig.fetch(expiration: const Duration(seconds: 0));
It will fetch the latest values every time but this is not recommended as you can also get FetchThrottledException.
Future<RemoteConfig> setupRemoteConfig() async {
final RemoteConfig remoteConfig = RemoteConfig.instance;
await remoteConfig.ensureInitialized();
await remoteConfig.fetchAndActivate();
var hello = remoteConfig.getString("hello");
print("hello: |${hello}|");
return remoteConfig;
}
await remoteConfig.fetchAndActivate(); is very important.
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.