How can I get native MediaStreamTrack from WebRtc MediaStreamTrackWeb object - dart

I want to mix MediaStreamTrack objects in Dart using the package:universal_html/js.dart library.
JsAudioContext audioContext = JsAudioContext();
audioContext.initialize();
var senders = await call!.peerConnection!.getSenders();
for (var sender in senders) {
for (var track in senderTracks) {
if (sender.track!.id != track.id) {
audioContext.connect(track);
}
}
}
But WebRtc hides the jsTrack native object inside the MediaStreamTrackWeb object.
How can I access this object ?
Is there anyone have an idea ?

I found the solution using the js_bindings library.
The library's MediaStream.getTracks() method throws a type error.
I solved this problem using js_util interop.
JsAudioContext.dart:
import 'dart:convert';
import 'package:flutter_webrtc/flutter_webrtc.dart' as webrtc;
import 'package:dart_webrtc/src/media_stream_track_impl.dart' as track_impl;
import 'package:js_bindings/js_bindings.dart' as js_bindings;
import 'package:universal_html/html.dart' as html;
import 'dart:js_util' as js_util;
class JsAudioContext {
js_bindings.AudioContext? audioContext;
js_bindings.MediaStreamAudioDestinationNode? destinationNode;
JsAudioContext() {
audioContext = js_bindings.AudioContext();
}
void createMediaStreamDestination() {
destinationNode = audioContext?.createMediaStreamDestination();
}
void connect(webrtc.MediaStreamTrack? trackWeb) {
track_impl.MediaStreamTrackWeb mediaStreamTrackWeb =
trackWeb as track_impl.MediaStreamTrackWeb;
html.MediaStreamTrack htmlTrack = mediaStreamTrackWeb.jsTrack;
var sourceStream = audioContext?.createMediaStreamSource(
js_bindings.MediaStream([htmlTrack as js_bindings.MediaStreamTrack]));
sourceStream?.connect(destinationNode!);
}
webrtc.MediaStreamTrack getMixedTrack() {
List<dynamic> outputTrack =
js_util.callMethod(destinationNode!.stream, 'getTracks', []);
webrtc.MediaStreamTrack rtcTrack = track_impl.MediaStreamTrackWeb(
outputTrack.toList()[0] as html.MediaStreamTrack);
return rtcTrack;
}
}
sip_call_event_service.dart:
#override
Future startConference(List<SipCallData> activeCallList) async {
List<webrtc.MediaStreamTrack> receivedTracks = <webrtc.MediaStreamTrack>[];
for (var item in activeCallList) {
Call? call = sipuaHelper!.findCall(item.id!);
var receives = await call!.peerConnection!.getReceivers();
for (var element in receives) {
receivedTracks.add(element.track!);
}
}
JsAudioContext jsAudioContext = JsAudioContext();
for (var item in activeCallList) {
Call? call = sipuaHelper!.findCall(item.id!);
jsAudioContext.createMediaStreamDestination();
var receivers = await call!.peerConnection!.getReceivers();
for (var receiver in receivers) {
for (var track in receivedTracks) {
if (receiver.track!.id != track.id) {
jsAudioContext.connect(track);
}
}
}
var senders = await call.peerConnection!.getSenders();
for (var sender in senders) {
jsAudioContext.connect(sender.track);
}
await senders.first.replaceTrack(jsAudioContext.getMixedTrack());
}
}

Related

How fix this null check issue?

Dart Language:
This is plugin issue
xmpp_stone plugin
I can't fix this issue
anyone known please replay
[![enter image description here]
https://i.stack.imgur.com/FHe0O.png
Dart Language:
This is plugin issue
xmpp_stone plugin
I can't fix this issue
anyone known please replay
[![enter image description here]
https://i.stack.imgur.com/FHe0O.png
This is full code issue in xmpp_stone plugin
import 'dart:io';
import 'package:xmpp_stone/src/logger/Log.dart';
import 'package:console/console.dart';
import 'dart:async';
import 'dart:convert';
import 'package:xmpp_stone/xmpp_stone.dart' as xmpp;
import 'package:image/image.dart' as image;
final String TAG = 'example';
class ExampleConnectionStateChangedListener implements xmpp.ConnectionStateChangedListener {
late xmpp.Connection _connection;
late xmpp.MessagesListener _messagesListener;
StreamSubscription<String>? subscription;
ExampleConnectionStateChangedListener(xmpp.Connection connection, xmpp.MessagesListener messagesListener) {
_connection = connection;
_messagesListener = messagesListener;
_connection.connectionStateStream.listen(onConnectionStateChanged);
}
#override
void onConnectionStateChanged(xmpp.XmppConnectionState state) {
if (state == xmpp.XmppConnectionState.Ready) {
Log.d(TAG, 'Connected');
_connection.getMamModule().queryAll();
var vCardManager = xmpp.VCardManager(_connection);
vCardManager.getSelfVCard().then((vCard) {
if (vCard != null) {
Log.d(TAG, 'Your info' + vCard.buildXmlString());
}
});
var messageHandler = xmpp.MessageHandler.getInstance(_connection);
var rosterManager = xmpp.RosterManager.getInstance(_connection);
messageHandler.messagesStream.listen(_messagesListener.onNewMessage);
sleep(const Duration(seconds: 1));
var receiver = 'yyy#gmail.com';
var receiverJid = xmpp.Jid.fromFullJid(receiver);
rosterManager.addRosterItem(xmpp.Buddy(receiverJid)).then((result) {
if (result.description != null) {
print("TAG, 'add roster'" + result.description!);
}
});
sleep(const Duration(seconds: 1));
vCardManager.getVCardFor(receiverJid).then((vCard) {
if (vCard != null) {
print("TAG, 'Receiver info'" + vCard.buildXmlString());
if (vCard != null && vCard.image != null) {
var file = File('test456789.jpg')..writeAsBytesSync(image.encodeJpg(vCard.image!));
print("TAG, IMAGE SAVED TO: ${file.path}");
}
}
});
var presenceManager = xmpp.PresenceManager.getInstance(_connection);
presenceManager.presenceStream.listen(onPresence);
}
}
void onPresence(xmpp.PresenceData event) {
Log.d(TAG, 'presence Event from ' + event.jid!.fullJid! + ' PRESENCE: ' + event.showElement.toString());
}
}
Stream<String> getConsoleStream() {
return Console.adapter.byteStream().map((bytes) {
var str = ascii.decode(bytes);
str = str.substring(0, str.length - 1);
return str;
});
}
class ExampleMessagesListener implements xmpp.MessagesListener {
#override
void onNewMessage(xmpp.MessageStanza? message) {
if (message!.body != null) {
Log.d(TAG ,format(
'New Message from {color.blue}${message.fromJid!.userAtDomain}{color.end} message: {color.red}${message.body}{color.end}'));
}
}
#override
void onChatMessage(xmpp.MessageStanza? message) {
print(message);
if (message!.body != null) {
Log.d(TAG,format(
'New Message from {color.blue}${message.fromJid!.userAtDomain}{color.end} message: {color.red}${message.body}{color.end}'));
}
}
}
sendmessageforxmpp(){
var userAtDomain = 'xxx#gmail.com';
var password = 'password';
var jid = xmpp.Jid.fromFullJid(userAtDomain);
var account = xmpp.XmppAccountSettings(userAtDomain, jid.local,
jid.domain, password, 5222, resource: 'xmppstone');
var connection = xmpp.Connection(account);
var receiver = 'yyy#gmail.com';
var receiverJid = xmpp.Jid.fromFullJid(receiver);
Log.d(TAG, receiverJid.fullJid.toString());
var messageHandler =
xmpp.MessageHandler.getInstance(connection);
messageHandler.sendMessage(receiverJid, "str");
}
Your problem is that you are not using xmpp_stone correctly and therefore ends up in a situation where the internal state of xmpp_stone does not match what the developer of the package have intended.
I do, however, think the package are badly designed in such a way that wrong usage are very likely to happen so I would not blame you for getting into trouble.
The problem is the following in your code:
var connection = xmpp.Connection(account);
// ..
var messageHandler = xmpp.MessageHandler.getInstance(connection);
messageHandler.sendMessage(receiverJid, "str");
You are here creating a Connection but the underlying socket are never created. The default value for the internal state of Connection are XmppConnectionState.Idle. But when you are later trying to sendMessage, your code ends up running this from the package:
void write(message) {
Log.xmppp_sending(message);
if (isOpened()) {
_socket!.write(message);
}
}
bool isOpened() {
return state != XmppConnectionState.Closed &&
state != XmppConnectionState.ForcefullyClosed &&
state != XmppConnectionState.Closing &&
state != XmppConnectionState.SocketOpening;
}
The isOpened() ends up returning true since it sees XmppConnectionState.Idle as an open state where messages are allowed to be sent.
But that is not the case here since we never asked Connection to open actually do any connection and therefore _socket ends up being null. Since the package are trying to do ! on null, the application crashes.
For an actual solution, we can get inspired from the example implementation from xmpp_dart:
https://github.com/vukoye/xmpp_dart/blob/master/example/example.dart
We can here see they have a connection.connect(); call. But, I am going to guess this really only works because the example are not going to use the connection right after this call. The problem is that it is implemented like the following:
void connect() {
if (_state == XmppConnectionState.Closing) {
_state = XmppConnectionState.WouldLikeToOpen;
}
if (_state == XmppConnectionState.Closed) {
_state = XmppConnectionState.Idle;
}
if (_state == XmppConnectionState.Idle) {
openSocket();
}
}
Future<void> openSocket() async {
connectionNegotatiorManager.init();
setState(XmppConnectionState.SocketOpening);
try {
return await Socket.connect(account.host ?? account.domain, account.port)
.then((Socket socket) {
// if not closed in meantime
if (_state != XmppConnectionState.Closed) {
setState(XmppConnectionState.SocketOpened);
So connect() returns void but calls openSocket() which does return a Future that would be able to tell us when the connection are actually ready.
I would therefore instead suggest using openSocket() directly and make your sendmessageforxmpp() method async so we can await on the connection being open.
So your code should look like:
Future<void> sendmessageforxmpp() async {
var userAtDomain = 'xxx#gmail.com';
var password = 'password';
var jid = xmpp.Jid.fromFullJid(userAtDomain);
var account = xmpp.XmppAccountSettings(
userAtDomain, jid.local, jid.domain, password, 5222,
resource: 'xmppstone');
var connection = xmpp.Connection(account);
await connection.openSocket(); // <--- the important change :)
var receiver = 'yyy#gmail.com';
var receiverJid = xmpp.Jid.fromFullJid(receiver);
Log.d(TAG, receiverJid.fullJid.toString());
var messageHandler = xmpp.MessageHandler.getInstance(connection);
messageHandler.sendMessage(receiverJid, "str");
}
This error is usually occurring when you use the bang operator (!) on a nullable value that was not properly initialized, like
yourvariable!.somefield
The above assumes that yourvariable will not be null as this point. If it's null, then reality is in conflict with the assumption I have just described.

return List of Strings from Future / Stream

My Problem is very similar to the one mentioned here and here, but for some reason these are not working for me.
Basically, I want to do some simple I/O operations (on a mobile), returning of list of strings (folder path) that contain a certain file format (let's assume for the sake of argument that I want to find all mp3 files).
This is the code I have
Future<List<String>> getFolders() async {
List<String> _dirs = new List();
await SimplePermissions.requestPermission(Permission.ReadExternalStorage);
_dirs = await findAllFolders();
return _dirs;
}
Future<List<String>> findAllFolders() async {
Directory _root = Directory("/sdcard");
bool _notInList = true;
List<String> _dirs = new List();
_root.list(recursive: true, followLinks: false)
.listen((FileSystemEntity entity) {
if(entity.toString().contains("mp3")) {
if(_dirs.length==0) {
_dirs.add(entity.parent.path.toString());
} else {
_notInList = true;
for (var i = 0; i < _dirs.length; ++i) {
if(_dirs[i] == entity.parent.path.toString()) {
_notInList = false;
}
}
if(_notInList) {
_dirs.add(entity.parent.path.toString());
}
}
}
});
return _dirs;
}
where I want to use _dirs outside of getFolders().
I know that findAllFolders() returns _dirs immediately, before my listen() event has finished (and so its length is always 0, although the actual method works fine, i.e. if I put print statements where I have _dirs.add() I can see that the correct directories are added, _dirs contains what I want but I have no idea how to return the finished _dirs list). I tried to do something in a similar way to the above mentioned post, where a Completer is used (to which I am getting an error message "Bad State: Future already completed"). The respective code would be
Future<List<String>> findAllFolders() async {
Directory _root = Directory("/sdcard");
bool _notInList = true;
List<String> _dirs = new List();
Completer<List<String>> _completer = new Completer<List<String>>();
_root.list(recursive: true, followLinks: false)
.listen((FileSystemEntity entity) {
if(entity.toString().contains("mp3")) {
if(_dirs.length==0) {
_dirs.add(entity.parent.path.toString());
} else {
_notInList = true;
for (var i = 0; i < _dirs.length; ++i) {
if(_dirs[i] == entity.parent.path.toString()) {
_notInList = false;
}
}
if(_notInList) {
_dirs.add(entity.parent.path.toString());
}
}
}
_completer.complete(_dirs);
});
return _completer.future;
}
The getFolders() function remains the same in this case. Could anyone point out where my logic is going wrong?
You're setting a listener, then immediately returning before any results are received - that's why your return is always empty. The body of findAllFolders() needs to wait for a response before returning. Try the below to replace _root.list().listen():
List<FileSystemEntity> files = await _root.list(recursive: true, followLinks: false).toList();
for (FileSystemEntity entity in files) {
// Do your filename logic and populate _dirs

ActionScript 3.0 and 'TypeError: Error#1034: type Coercion failed'

I'm currently following a tutorial, and I watched it like 6-7 times over, but for some reason I keep getting:
TypeError: Error#1034: type Coercion failed.
I'm trying to make a matching game for a school assignment, and I currently have this:
package {
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.events.Event;
import flash.events.MouseEvent;
public class MatchingGame extends MovieClip {
var fClip:Logo
var sClip:Logo
var myTimer:Timer
var frames:Array = new Array(1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10);
public function MatchingGame() {
// Constructor code
for(var i:Number=1; i<=5; i++) {
for(var j:Number=1; j<=4; j++) {
var myLogo:Logo = new Logo();
var index = Math.floor(Math.random() * frames.length)
myLogo.frameNo = frames[index];
frames.splice(index, 1);
addChild(myLogo);
myLogo.x = j*100;
myLogo.y = i*100;
myLogo.gotoAndStop(11);
myLogo.addEventListener(MouseEvent.CLICK, openLogo);
}
}
}
private function openLogo(e:MouseEvent) {
var clickObj:Logo = Logo(e.target);
if(fClip == null) {
fClip = clickObj;
fClip.gotoAndStop(fClip.frameNo);
}
else if(sClip == null && fClip != clickObj) {
sClip = clickObj;
sClip.gotoAndStop(sClip.frameNo);
if(fClip.frameNo == sClip.frameNo) {
myTimer = new Timer(1000, 1);
myTimer.start();
myTimer.addEventListener(TimerEvent.TIMER_COMPLETE, removeLogos);
}
else {
myTimer = new Timer(1000, 1);
myTimer.start();
myTimer.addEventListener(TimerEvent.TIMER_COMPLETE, resetLogos);
}
}
}
private function removeLogos(e:TimerEvent) {
removeChild(fClip);
removeChild(sClip);
myTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, removeLogos);
fClip = null;
sClip = null;
}
private function resetLogos(e:TimerEvent) {
fClip.gotoAndStop(11);
sClip.gotoAndStop(11);
myTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, resetLogos);
fClip = null;
sClip = null;
}
}
}
The error pops up at line 38 and when I try debugging it tell me that clickObj is undefined.
How can I fix this problem?
This is the entire error message:
TypeError: Error #1034: Type Coercion failed: cannot convert flash.display::MovieClip#a3e4a61 to Logo.
at MatchingGame/openLogo()[H:\Informatica\Matching game\MatchingGame.as:39]
It looks like the cast from MovieClip to Logo isn't working.
put a trace before that line to see what event.target is.
Depending on the display list structure and event bubbling, you might be getting a different element that what you expect.
Try var clickObj:Logo = Logo(e.currentTarget); as a quick test.
Be sure to go through Trevor McCauley's article to get a better understanding of event bubbling.

Youtube Livestream Api LiveChatMessages List

Im trying to get the Messages from a Youtube Livestream, works, but i dont get new Messages. The NextPageToken is included.
Sometimes i get new messages, but it takes arround 5-10min.
Youtube Chat Sending works also fine.
Any Idea?
This is from the Docs: https://developers.google.com/youtube/v3/live/docs/liveChatMessages/list
private async Task GetMessagesAsync(string liveChatId, string nextPageToken, long? pollingIntervalMillis)
{
liveChatId = "EiEKGFVDVUQ3WGNXTk92SlpvaHFMM3dZTi1uZxIFL2xpdmU";
if (!updatingChat)
{
if (!string.IsNullOrEmpty(liveChatId))
{
newMessages = true;
var chatMessages = youTubeService.LiveChatMessages.List(liveChatId, "id,snippet,authorDetails");
var chatResponse = await chatMessages.ExecuteAsync();
PageInfo pageInfo = chatResponse.PageInfo;
newMessages = false;
if (pageInfo.TotalResults.HasValue)
{
if (!prevCount.Equals(pageInfo.TotalResults.Value))
{
prevCount = pageInfo.TotalResults.Value;
newMessages = true;
}
}
if (newMessages)
{
Messages = new List<YouTubeMessage>();
foreach (var chatMessage in chatResponse.Items)
{
string messageId = chatMessage.Id;
string displayName = chatMessage.AuthorDetails.DisplayName;
string displayMessage = chatMessage.Snippet.DisplayMessage;
string NextPagetoken = chatResponse.NextPageToken;
YouTubeMessage message = new YouTubeMessage(messageId, displayName, displayMessage);
if (!Messages.Contains(message))
{
Messages.Add(message);
string output = "[" + displayName + "]: " + displayMessage;
Console.WriteLine(time + output);
}
}
}
await GetMessagesAsync(liveChatId, chatResponse.NextPageToken, chatResponse.PollingIntervalMillis);
}
}
updatingChat = false;
await Task.Delay(100);
}
public async Task YouTubeChatSend(string message)
{
try
{
LiveChatMessage liveMessage = new LiveChatMessage();
liveMessage.Snippet = new LiveChatMessageSnippet()
{
LiveChatId = "EiEKGFVDVUQ3WGNXTk92SlpvaHFMM3dZTi1uZxIFL2xpdmU",
Type = "textMessageEvent",
TextMessageDetails = new LiveChatTextMessageDetails() { MessageText = message }
};
var insert = this.youTubeService.LiveChatMessages.Insert(liveMessage, "snippet");
var response = await insert.ExecuteAsync();
if (response != null)
{
}
}
catch
{
Console.WriteLine("Failed to chat send");
}
}

How make my own Stream

I have already try to understand the API doc, the articles about them, and this post: How do you create a Stream in Dart
I'm making a simple web app using WebSocket. Actually, it's working well, but I want add a feature (enjoy learn).
This is my class (can be optimized I guess)
library Ask;
import 'dart:html';
import 'dart:async';
import 'dart:convert';
class Ask {
final String addr;
String _protocol;
String _port;
WebSocket _ws;
bool openned;
Map<int, Completer> _completer_list = {};
int _counter = 0;
static final Map<String, Ask> _cache = <String, Ask>{};
factory Ask(String addr) {
if (_cache.containsKey(addr)) {
return _cache[addr];
} else {
final ask_server = new Ask._internal(addr);
_cache[addr] = ask_server;
return ask_server;
}
}
Ask._internal(this.addr);
Future<bool> open() {
if (openned)
return true;
_completer_list[0] = new Completer();
if (window.location.protocol == 'http:') {
_port = ':8080/ws';
_protocol = 'ws://';
} else {
_port = ':8443/ws';
_protocol = 'wss://';
}
_ws = new WebSocket(_protocol + addr + _port);
_ws.onOpen.listen((e) {
_get_data();
_get_close();
openned = true;
_completer_list[0].complete(true);
});
return _completer_list[0].future;
}
Future<String> send(Map data) {
bool check = false;
int id;
_completer_list.forEach((k, v) {
if (v.isCompleted) {
id = data['ws_id'] = k;
_completer_list[k] = new Completer();
_ws.send(JSON.encode(data));
check = true;
}
});
if (!check) {
_counter++;
id = data['ws_id'] = _counter;
_completer_list[id] = new Completer();
_ws.send(JSON.encode(data));
}
return _completer_list[id].future;
}
void _get_data() {
_ws.onMessage.listen((MessageEvent data) {
var response = JSON.decode(data.data);
_completer_list[response['ws_id']].complete(response);
});
}
void _get_close() {
_ws.onClose.listen((_) {
print('Server have been lost. Try to reconnect in 3 seconds.');
new Timer(new Duration(seconds: 3), () {
_ws = new WebSocket(_protocol + addr + _port);
_get_data();
_get_close();
_ws.onOpen.listen((e) => print('Server is alive again.'));
});
});
}
}
Example of use:
void showIndex() {
Element main = querySelector('main');
Ask connect = new Ask('127.0.0.1');
Map request = {};
request['index'] = true;
connect.open().then((_) {
connect.send(request).then((data) {
main.setInnerHtml(data['response']);
});
});
}
I would replace the then by a listen who will be canceled when the message will completed. By this way, I can add a progress bar, I think...
So my question, my send function can be a stream and keep my concept of one websocket for all ? (yes, if my function is used when a request is in progress, it's sent and if she's finish before the first, I recovered her properly. Thank you ws_id).
Thank you.
I think what you need is a StreamController
https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart-async.StreamController

Resources