I am using the Jitsi Flutter plugin in conjunction with 8x8's Jitsi-as-a-Service offering to integrate video calls into my mobile app.
Immediately after joining a meeting, the call ends and Jitsi closes. The logs indicate that the underlying Jitsi Meet SDK is unable to fetch config.js from 8x8:
E/JitsiMeetSDK(10937): [features/base/lib-jitsi-meet] Failed to load config from https://8x8.vc/vpaas-magic-cookie-<tenantID>/hello-world/config.js?room=vpaas-magic-cookie-<tenantID>hello-world Error(Error){"message":"loadScript error: undefined","stack":"index.android.bundle:538:817\np#index.android.bundle:284:423\nindex.android.bundle:284:1740\np#index.android.bundle:284:423\nn#index.android.bundle:284:898\nindex.android.bundle:284:1047\nf#index.android.bundle:111:155\nindex.android.bundle:111:882\ny#index.android.bundle:117:661\nC#index.android.bundle:117:1025\ncallImmediates#index.android.bundle:117:3100\ncallImmediates#[native code]\nvalue#index.android.bundle:36:3247\nindex.android.bundle:36:1283\nvalue#index.android.bundle:36:2939\nvalue#index.android.bundle:36:1253\nvalue#[native code]\nvalue#[native code]"}
I/JitsiMeetSDK(10937): [features/overlay] The conference will be reloaded after 19 seconds.
D/JitsiMeetSDK(10937): ExternalAPI Sending event: CONFERENCE_TERMINATED with data: { NativeMap: {"url":"https://8x8.vc/vpaas-magic-cookie-<tenantID>/hello-world/vpaas-magic-cookie-<tenantID>hello-world","error":"Error: loadScript error: undefined"} }
E/ThemeUtils(10937): View class com.facebook.react.views.text.ReactTextView is an AppCompat widget that can only be used with a Theme.AppCompat theme (or descendant).
I/chatty (10937): uid=10179(com.example.app) identical 1 line
E/ThemeUtils(10937): View class com.facebook.react.views.text.ReactTextView is an AppCompat widget that can only be used with a Theme.AppCompat theme (or descendant).
D/JITSI_MEET_PLUGIN(10937): JitsiMeetPluginActivity.onConferenceTerminated: {error=Error: loadScript error: undefined, url=https://8x8.vc/vpaas-magic-cookie-<tenantID>/hello-world/vpaas-magic-cookie-<tenantID>hello-world}
D/JITSI_MEET_PLUGIN(10937): JitsiMeetEventStreamHandler.onConferenceTerminated
I/JitsiMeetSDK(10937): Conference terminated: {error=Error: loadScript error: undefined, event=onConferenceTerminated, url=https://8x8.vc/vpaas-magic-cookie-<tenantID>/hello-world/vpaas-magic-cookie-<tenantID>hello-world}
I/JitsiMeetSDK(10937): [features/base/connection] No connection found while disconnecting.
I followed the official 8x8 JaaS docs on how to configure Jitsi in my app:
_joinMeeting(String token, String userName) async {
FeatureFlag featureFlag = FeatureFlag();
featureFlag.welcomePageEnabled = false;
// ...
featureFlag.resolution = FeatureFlagVideoResolution.HD_RESOLUTION;
var options = JitsiMeetingOptions()
..serverURL = "https://8x8.vc"
..room = "vpaas-magic-cookie-<tenantID>/hello-world"
..subject = "Hello World"
..token = token
..userDisplayName = userName
..audioOnly = false
..audioMuted = true
..videoMuted = true
..featureFlag = featureFlag;
await JitsiMeet.joinMeeting(options, roomNameConstraints: Map());
}
It works flawlessly with the public meet.jit.si servers but does not with 8x8's JaaS offering. What am I missing?
Using 8x8's JaaS offering with the Jitsi Flutter plugin
TL;DR: Set the full URL of your 8x8 meeting as the room name.
If you follow the official 8x8 Jaas docs, you will likely end up with Jitsi Flutter being unable to load config.js from the 8x8 server.
This forum comment hints that one can join 8x8 meetings via the official Jitsi Meet app from the App Store by using the full 8x8 meeting URL as the room name.
As a matter of fact, this not only works for the official app, but also for the Jitsi Flutter plugin. Using the configuration below you should be able to connect to a meeting room on your 8x8 tenant. Please make sure to disable any room name constraints by passing an empty map to joinMeeting, otherwise Jitsi Flutter will reject your room name!
_joinMeeting(String token, String userName) async {
FeatureFlag featureFlag = FeatureFlag();
featureFlag.welcomePageEnabled = false;
// ...
featureFlag.resolution = FeatureFlagVideoResolution.HD_RESOLUTION;
var options = JitsiMeetingOptions()
..serverURL = "https://8x8.vc"
..room = "https://8x8.vc/vpaas-magic-cookie-<tenantID>/<roomName>"
..subject = "Hello World"
..token = token
..userDisplayName = userName
..audioOnly = false
..audioMuted = true
..videoMuted = true
..featureFlag = featureFlag;
await JitsiMeet.joinMeeting(options, roomNameConstraints: Map());
}
Hope this saves you some time.
Related
I have following problem. I just can't figure out how to initialize WalletConnect and connect to Metamask on Android. I always get following exception.
java.lang.NoSuchMethodError: No interface method getInitializationErrorsFlow()Lkotlinx/coroutines/flow/Flow; in class Lcom/walletconnect/android/internal/common/model/JsonRpcInteractorInterface;
My code looks as follows:
val projectId = "15f2b4ae8bb12dsd3d267ce6441d5a9d" // I got the Project ID from https://cloud.walletconnect.com/
val relayUrl = "relay.walletconnect.com"
val serverUrl = "wss://$relayUrl?projectId=$projectId"
val connectionType = ConnectionType.AUTOMATIC
val appMetaData = Core.Model.AppMetaData( // <-- What data should be entered here?
name = "Metamask",
description = "Description",
url = "Wallet Url", // How can I get the wallet url?
icons = listOf(""),
redirect = "kotlin-wallet-wc:/request"
)
CoreClient.initialize(relayServerUrl = serverUrl, connectionType = connectionType, application = application, metaData = appMetaData)
val init = Sign.Params.Init(CoreClient)
SignClient.initialize(init, onError = { error ->
})
Are you calling all initialize methods inside the Application onCreate() method? We have a demo wallet to showcase how it should be done here
enter code here
wc:a593bc04-32b5-4e25-aaec-9e2c247e868a#1?bridge=https%3A%2F%2Fv.bridge.walletconnect.org&key=be7212e692f047b39425b0fe88d805582ac29aa76ccb893d5c0cdef8845dc3c3
All the answers are from this tutorial: Connect a iOS or Android DApp to Web3 Wallet. I strongly recommend reading it.
1. "What data should be entered here?":
It's the data about your DApp, it allows the end user to recognize and decide to grant you rights when his wallet asks for it.
Here is a simple example:
private val metaData = Session.PeerMeta(
name = "nfscene",
url = "nfscene.com",
description = "nfscene WalletConnect demo"
)
2. "How can I get the wallet URL?"
You need to understand that the end-user can choose his wallet. So you can't force the wallet to be used. Knowing what Wallet the user use is not your concern as the transactions are sent through the Wallet Connect bridge. If you only want to allow the use of MetaMask you should directly use the MetaMask API.
you using 2.0.0?
it was fixed in 2.1.0.
According to GitHub sample project and Tealium's documentation for Kotlin I created such TealiumHelper:
object TealiumHelper {
fun init(application: Application) {
val tealiumConfig = TealiumConfig(
application,
accountName = BuildConfig.TEALIUM_ACCOUNT_NAME,
profileName = BuildConfig.TEALIUM_PROFILE_NAME,
environment = BuildConfig.TEALIUM_ENVIRONMENT
)
// Display logs only for DEV
Logger.Companion.logLevel = BuildConfig.TEALIUM_LOGLEVEL
// Make it start working
Tealium.create(BuildConfig.TEALIUM_INSTANCE, tealiumConfig)
}
fun trackEvent(name: String, data: Map<String, Any>? = null) {
val eventDispatch = TealiumEvent(name, data)
Tealium[BuildConfig.TEALIUM_INSTANCE]?.track(eventDispatch)
}
fun trackView(name: String, data: Map<String, Any>? = null) {
val viewDispatch = TealiumView(name, data)
Tealium[BuildConfig.TEALIUM_INSTANCE]?.track(viewDispatch)
}
}
I get logs by Tealium so it should be working fine.
2021-05-17 14:28:56.694 22812-22894/xxx.xxx.xxx D/Tealium-1.2.2: Dispatch(fc5c0) - Ready - {tealium_event_type=view, tealium_event=XXX ...}
But after I call trackView or trackEvent, my events don't go to server.
There is also additional log infor which I don't know what does it mean. Documentation doesn't say much about it:
2021-05-17 14:28:59.352 22812-22894/xxx.xxx.xxx I/Tealium-1.2.2: Asset not found (tealium-settings.json)
How could I fix it? What does Asset not found mean?
#deadfish I manage the mobile team here at Tealium, so I can point you in the right direction. You can safely ignore the Asset Not Found log - this just indicates that no local settings file was found, so by default, the remote settings will be used. We'll look at addressing this to make the log message a little more helpful. There are a couple of things that might cause data not to be sent. Firstly, I can't see any Dispatchers specified in your TealiumConfig object. As specified in the docs, you need to add either the TagManagement or Collect dispatcher, which are the modules that send the data from the device. The Collect module sends data to the server-side platform (I think this is what you need), while the TagManagement module processes the data client-side using JavaScript. Please feel free to get in touch with our support team or contact us by raising an issue on our GitHub repo if you need further personalized support.
I am currently developing an Android application using Cordova. This working fine so far, but now that I want to add a Chromecast button to the UI, it does not seem to work. I followed the instructions provided here: https://developers.google.com/cast/docs/chrome_sender/integrate
And this is what my code looks so far:
var CastPlayer = function() {
//...
/* Cast player variables */
/** #type {cast.framework.RemotePlayer} */
this.remotePlayer = null;
/** #type {cast.framework.RemotePlayerController} */
this.remotePlayerController = null;
//...
};
var castPlayer = new CastPlayer();
window['__onGCastApiAvailable'] = function(isAvailable) {
if (isAvailable) {
castPlayer.initializeCastPlayer();
}
};
Inline script inside my index.html.
CastPlayer.prototype.initializeCastPlayer = function() {
var options = {};
// Set the receiver application ID to your own (created in the
// Google Cast Developer Console), or optionally
// use the chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID
options.receiverApplicationId = chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID;
// Auto join policy can be one of the following three:
// ORIGIN_SCOPED - Auto connect from same appId and page origin
// TAB_AND_ORIGIN_SCOPED - Auto connect from same appId, page origin, and tab
// PAGE_SCOPED - No auto connect
options.autoJoinPolicy = chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED;
cast.framework.CastContext.getInstance().setOptions(options);
this.remotePlayer = new cast.framework.RemotePlayer();
this.remotePlayerController = new cast.framework.RemotePlayerController(this.remotePlayer);
this.remotePlayerController.addEventListener(
cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED,
this.switchPlayer.bind(this)
);
};
Content of my index.js.
In the index.html, I added the button like this:
<google-cast-launcher id="castbutton"></google-cast-launcher>
Now when I open my Cordova app via browser (Chrome AND Chromium), the cast button shows and I can use it normally. When I open the App on Android, the Button just does not show. Does anybody know what causes this and if it can be resolved?
We could not find a solution for Cordova, but managed it in Flutter.
We had the same challenge, and we searched high and low. Finally found this solution to make it work with: https://pub.dev/packages/dart_chromecast
Make sure your flutter compiler is downgraded to 13 or below. Otherwise, you will not be able to compile. Unfortunately, their code is not supported in a newer version of the flutter compiler and the author is not going to update anytime soon.
I'm trying to authenticate an end-user in an android app written in C# (Xamarin.Android).
I decided to try and use NuGet package Google.Apis.Oauth.v2 which seems to expose an easy to use Oauth client.
LocalServerCodeReceiver.ReceiveCodeAsync throws the following:
I get System.NotSupportedException:"Failed to launch browser with https://XXX.auth.XXX.amazoncognito.com/login?response_type=token&client_id=XXX&redirect_uri=https%3A%2F%2Fwww.google.com&scope=profile%20openid%20email for authorization. See inner exception for details."
and it has an inner exception of System.ComponentModel.Win32Exception:"Cannot find the specified file"
Code:
var clientSecret = new Google.Apis.Auth.OAuth2.ClientSecrets();
clientSecret.ClientId = ...
clientSecret.ClientSecret = ...
var initializer = new Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.Initializer(
"https://XXX.auth.XXX.amazoncognito.com/login",
"https://XXX.auth.XXX.amazoncognito.com/login");
initializer.Scopes = new List<string> {"profile", "openid", "email"};
initializer.ClientSecrets = clientSecret;
var flow = new Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow(initializer);
var authCodeRequestURL = flow.CreateAuthorizationCodeRequest("https://www.google.com");
authCodeRequestURL.ResponseType = "token";
var uri = authCodeRequestURL.Build();
var cancellationTokenSource = new System.Threading.CancellationTokenSource();
var codeReceiver = new Google.Apis.Auth.OAuth2.LocalServerCodeReceiver();
var task = codeReceiver.ReceiveCodeAsync(authCodeRequestURL, cancellationTokenSource.Token);
Do I need to ask for a specific permission in the application manifest?
Instead of redirecting to www.google.com, I've heard you can redirect to an app, I'm not really sure how to do that, is it http://my_app_package_name or http://my_app_title, something else?
Is it possible not to rely on that library for launching the browser and instead get the RequestUri and start an Activity, if so how will the app become aware the end-user completed the SignIn process and how will the app retrieve the token?
Sorry, but Google.Apis.Oauth.v2 does not support Xamarin, and there's no simple way to get it working.
Unfortunately no Google.Apis.* packages currently support Xamarin.
You might find the Xamarin.Auth package does what you want?
I've figured out how to redirect to an app after authentication in the browser completes.
It's called a "Deep Link" and it's documented at enter link description here, essentially you need to declare an IntentFilter on your Activity, which registers with the Android OS that if someone clicks or an page redirects to a specific URI, your app gets called. The token that's appended to the URI can then be read inside your app.
I am using DynamoDB as back-end database for my mobile app, and the schema etc are identical across Android & iOS. For a particular use-case, I have to perform a Scan, based on two attributes which are not indexed. For iOS Objective C, I am using the following code:
AWSDynamoDBScanExpression *scanExpression = [AWSDynamoDBScanExpression new];
scanExpression.limit = [NSNumber numberWithInt:maxCount];
scanExpression.filterExpression = #"#l = :location AND event = :event";
scanExpression.expressionAttributeNames = #{#"#l":#"location"};
scanExpression.expressionAttributeValues = #{#":location":location,
#":event":EVENT_TASTING};
Both location and event are Strings. EVENT_TASTING is a String constant. This scan keeps returning zero results, even though I have validated that for the provided entries I should be receiving the results. I use the following code in Android Java:
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
scanExpression.setLimit(maxCount);
scanExpression.addFilterCondition("location",
new Condition()
.withComparisonOperator(ComparisonOperator.EQ)
.withAttributeValueList(new AttributeValue().withS(location)));
scanExpression.addFilterCondition("event",
new Condition()
.withComparisonOperator(ComparisonOperator.EQ)
.withAttributeValueList(new AttributeValue().withS(Constants.EVENT_TASTING)));
The scan works as expected in Android. What needs to change in iOS to make it work there too? I updated iOS SDK to 2.3.6 but it has not made a difference. This is the only scan operation I am doing in my code.
Is there an error in my scanExpression for iOS? Is there a way I can use the Android-style syntax to make this work on iOS?
Update
I tried the following changes:
AWSDynamoDBScanExpression *scanExpression = [AWSDynamoDBScanExpression new];
AWSDynamoDBAttributeValue *locationVal = [AWSDynamoDBAttributeValue new];
locationVal.S = location;
AWSDynamoDBAttributeValue *eventVal = [AWSDynamoDBAttributeValue new];
eventVal.S = EVENT_TASTING;
scanExpression.limit = [NSNumber numberWithInt:maxCount];
scanExpression.filterExpression = #"#l = :location AND event = :event";
scanExpression.expressionAttributeNames = #{#"#l":#"location"};
scanExpression.expressionAttributeValues = #{#":location":locationVal,
#":event":eventVal};
But now I am getting an error:
The request failed. Error: [Error Domain=com.amazonaws.AWSDynamoDBErrorDomain Code=0 "(null)" UserInfo={message=ExpressionAttributeValues contains invalid value: Supplied AttributeValue is empty, must contain exactly one of the supported datatypes for key :location, __type=com.amazon.coral.validate#ValidationException}]
Thanks to the hint from #YosukeMatsuda, I was able to fix this by calling Scan repeatedly until LastEvaluatedKey is empty. I am posting this as answer because unfortunately Mike's answer is not pointing out the correct issue and is misleading.
Here's how I changed the code in iOS:
// In a different method (for first call):
AWSDynamoDBScanExpression *scanExpression = // See code in original question
// In a new method that can be called recursively:
// DynamoDBObjectMapper scan:class-for-model expression:scanExpression
// continueWithBlock -> if (task.result):
AWSDynamoDBPaginatedOutput *paginatedOutput = task.result;
if (paginatedOutput.items.count != 0)
// Append the paginatedOutput.items to the cumulative array
else
// Replace the cumulative array with paginatedOutput.items
if (paginatedOutput.lastEvaluatedKey.count == 0) {
// Scan is complete - handle results
} else {
// Check if you have sufficient results
// In my case I had asked for 25 results but was getting 39
// So it doesn't seem to obey the scanExpression.limit value
// If more results are needed, continue the scan
[scanExpression setExclusiveStartKey:paginatedOutput.lastEvaluatedKey];
// Call this method recursively
}
If there is a more elegant solution I'd love to hear it. But at least it works now.
There are several differences between the Android code you're using and the ObjectiveC version.
in the Android Version you're using the older Filter Condition API while in the ObjectiveC you're using the more modern Filter Expression API; this doesn't necessarily make the newer one fail but it's just something to point out
in the case of ExpressionAttributeValues, the values for location and event that you're passing in should be of type AWSDynamoDBAttributeValue *, not strings; if you make this change your query will most likely start working.
I hope this answers your question but can't be certain because you only say "this works as expected in Android - how can I make it work in iOS" but you're not telling us what's broken.