[Android] Allow multiple JitsiMeetViews
This commit is contained in:
parent
10e5e0fdf5
commit
4b2add7aa6
|
@ -30,6 +30,10 @@ import com.facebook.react.ReactRootView;
|
|||
import com.facebook.react.common.LifecycleState;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public class JitsiMeetView extends FrameLayout {
|
||||
/**
|
||||
|
@ -38,87 +42,24 @@ public class JitsiMeetView extends FrameLayout {
|
|||
*/
|
||||
private static final int BACKGROUND_COLOR = 0xFF111111;
|
||||
|
||||
/**
|
||||
* Reference to the single instance of this class we currently allow. It's
|
||||
* currently used for fetching the instance from the listener's callbacks.
|
||||
*
|
||||
* TODO: Lift this limitation.
|
||||
*/
|
||||
private static JitsiMeetView instance;
|
||||
|
||||
/**
|
||||
* React Native bridge. The instance manager allows embedding applications
|
||||
* to create multiple root views off the same JavaScript bundle.
|
||||
*/
|
||||
private static ReactInstanceManager reactInstanceManager;
|
||||
|
||||
/**
|
||||
* {@link JitsiMeetViewListener} instance for reporting events occurring in
|
||||
* Jitsi Meet.
|
||||
*/
|
||||
private JitsiMeetViewListener listener;
|
||||
private static final Set<JitsiMeetView> views
|
||||
= Collections.newSetFromMap(new WeakHashMap<JitsiMeetView, Boolean>());
|
||||
|
||||
/**
|
||||
* React Native root view.
|
||||
*/
|
||||
private ReactRootView reactRootView;
|
||||
|
||||
/**
|
||||
* Whether the Welcome page is enabled.
|
||||
*/
|
||||
private boolean welcomePageEnabled;
|
||||
|
||||
public JitsiMeetView(@NonNull Context context) {
|
||||
super(context);
|
||||
|
||||
if (instance != null) {
|
||||
throw new RuntimeException(
|
||||
"Only a single instance is currently allowed");
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Only allow a single instance for now. All React Native modules
|
||||
* are kinda singletons so global state would be broken since we have a
|
||||
* single bridge. Once we have that sorted out multiple instances of
|
||||
* JitsiMeetView will be allowed.
|
||||
*/
|
||||
instance = this;
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
if (reactInstanceManager == null) {
|
||||
initReactInstanceManager(((Activity) context).getApplication());
|
||||
public static JitsiMeetView findViewByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
for (JitsiMeetView view : views) {
|
||||
if (view.externalAPIScope.equals(externalAPIScope)) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the only instance of this class we currently allow creating.
|
||||
*
|
||||
* @return The {@code JitsiMeetView} instance.
|
||||
*/
|
||||
public static JitsiMeetView getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link JitsiMeetViewListener} set on this {@code JitsiMeetView}.
|
||||
*
|
||||
* @return The {@code JitsiMeetViewListener} set on this
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
public JitsiMeetViewListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the Welcome page is enabled. If {@code true}, the Welcome
|
||||
* page is rendered when this {@code JitsiMeetView} is not at a URL
|
||||
* identifying a Jitsi Meet conference/room.
|
||||
*
|
||||
* @return {@true} if the Welcome page is enabled; otherwise, {@code false}.
|
||||
*/
|
||||
public boolean getWelcomePageEnabled() {
|
||||
return welcomePageEnabled;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,7 +71,8 @@ public class JitsiMeetView extends FrameLayout {
|
|||
* @param application - <tt>Application</tt> instance which is running.
|
||||
*/
|
||||
private static void initReactInstanceManager(Application application) {
|
||||
reactInstanceManager = ReactInstanceManager.builder()
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
.setApplication(application)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModuleName("index.android")
|
||||
|
@ -141,65 +83,14 @@ public class JitsiMeetView extends FrameLayout {
|
|||
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
|
||||
.addPackage(new com.rnimmersive.RNImmersivePackage())
|
||||
.addPackage(new org.jitsi.meet.sdk.audiomode.AudioModePackage())
|
||||
.addPackage(new org.jitsi.meet.sdk.externalapi.ExternalAPIPackage())
|
||||
.addPackage(
|
||||
new org.jitsi.meet.sdk.externalapi.ExternalAPIPackage())
|
||||
.addPackage(new org.jitsi.meet.sdk.proximity.ProximityPackage())
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given URL and displays the conference. If the specified URL is
|
||||
* null, the welcome page is displayed instead.
|
||||
*
|
||||
* @param url - The conference URL.
|
||||
*/
|
||||
public void loadURL(@Nullable URL url) {
|
||||
Bundle props = new Bundle();
|
||||
|
||||
// url
|
||||
if (url != null) {
|
||||
props.putString("url", url.toString());
|
||||
}
|
||||
// welcomePageEnabled
|
||||
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
||||
|
||||
// TODO: ReactRootView#setAppProperties is only available on React
|
||||
// Native 0.45, so destroy the current root view and create a new one.
|
||||
if (reactRootView != null) {
|
||||
removeView(reactRootView);
|
||||
reactRootView = null;
|
||||
}
|
||||
|
||||
reactRootView = new ReactRootView(getContext());
|
||||
reactRootView
|
||||
.startReactApplication(reactInstanceManager, "App", props);
|
||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
||||
addView(reactRootView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific {@link JitsiMeetViewListener} on this
|
||||
* {@code JitsiMeetView}.
|
||||
*
|
||||
* @param listener - The {@code JitsiMeetViewListener} to set on this
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
public void setListener(JitsiMeetViewListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the Welcome page is enabled. Must be called before
|
||||
* {@link #loadURL(URL)} for it to take effect.
|
||||
*
|
||||
* @param welcomePageEnabled {@code true} to enable the Welcome page;
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
public void setWelcomePageEnabled(boolean welcomePageEnabled) {
|
||||
this.welcomePageEnabled = welcomePageEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* <tt>Activity.onBackPressed</tt> so we can do the required internal
|
||||
|
@ -269,4 +160,117 @@ public class JitsiMeetView extends FrameLayout {
|
|||
reactInstanceManager.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique identifier of this {@code JitsiMeetView} within the process
|
||||
* for the purposes of {@link ExternalAPI}. The name scope was inspired by
|
||||
* postis which we use on Web for the similar purposes of the iframe-based
|
||||
* external API.
|
||||
*/
|
||||
private final String externalAPIScope;
|
||||
|
||||
/**
|
||||
* {@link JitsiMeetViewListener} instance for reporting events occurring in
|
||||
* Jitsi Meet.
|
||||
*/
|
||||
private JitsiMeetViewListener listener;
|
||||
|
||||
/**
|
||||
* React Native root view.
|
||||
*/
|
||||
private ReactRootView reactRootView;
|
||||
|
||||
/**
|
||||
* Whether the Welcome page is enabled.
|
||||
*/
|
||||
private boolean welcomePageEnabled;
|
||||
|
||||
public JitsiMeetView(@NonNull Context context) {
|
||||
super(context);
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
if (reactInstanceManager == null) {
|
||||
initReactInstanceManager(((Activity) context).getApplication());
|
||||
}
|
||||
|
||||
// Hook this JitsiMeetView into ExternalAPI.
|
||||
externalAPIScope = UUID.randomUUID().toString();
|
||||
views.add(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link JitsiMeetViewListener} set on this {@code JitsiMeetView}.
|
||||
*
|
||||
* @return The {@code JitsiMeetViewListener} set on this
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
public JitsiMeetViewListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the Welcome page is enabled. If {@code true}, the Welcome
|
||||
* page is rendered when this {@code JitsiMeetView} is not at a URL
|
||||
* identifying a Jitsi Meet conference/room.
|
||||
*
|
||||
* @return {@true} if the Welcome page is enabled; otherwise, {@code false}.
|
||||
*/
|
||||
public boolean getWelcomePageEnabled() {
|
||||
return welcomePageEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given URL and displays the conference. If the specified URL is
|
||||
* null, the welcome page is displayed instead.
|
||||
*
|
||||
* @param url - The conference URL.
|
||||
*/
|
||||
public void loadURL(@Nullable URL url) {
|
||||
Bundle props = new Bundle();
|
||||
|
||||
// externalAPIScope
|
||||
props.putString("externalAPIScope", externalAPIScope);
|
||||
// url
|
||||
if (url != null) {
|
||||
props.putString("url", url.toString());
|
||||
}
|
||||
// welcomePageEnabled
|
||||
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
||||
|
||||
// TODO: ReactRootView#setAppProperties is only available on React
|
||||
// Native 0.45, so destroy the current root view and create a new one.
|
||||
if (reactRootView != null) {
|
||||
removeView(reactRootView);
|
||||
reactRootView = null;
|
||||
}
|
||||
|
||||
reactRootView = new ReactRootView(getContext());
|
||||
reactRootView
|
||||
.startReactApplication(reactInstanceManager, "App", props);
|
||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
||||
addView(reactRootView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific {@link JitsiMeetViewListener} on this
|
||||
* {@code JitsiMeetView}.
|
||||
*
|
||||
* @param listener - The {@code JitsiMeetViewListener} to set on this
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
public void setListener(JitsiMeetViewListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the Welcome page is enabled. Must be called before
|
||||
* {@link #loadURL(URL)} for it to take effect.
|
||||
*
|
||||
* @param welcomePageEnabled {@code true} to enable the Welcome page;
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
public void setWelcomePageEnabled(boolean welcomePageEnabled) {
|
||||
this.welcomePageEnabled = welcomePageEnabled;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,22 +62,31 @@ public class ExternalAPIModule extends ReactContextBaseJavaModule {
|
|||
/**
|
||||
* Dispatches an event that occurred on JavaScript to the view's listener.
|
||||
*
|
||||
* @param name - Event name.
|
||||
* @param data - Ancillary data for the event.
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
* by/associated with the specified {@code name}.
|
||||
* @param scope
|
||||
*/
|
||||
@ReactMethod
|
||||
public void sendEvent(final String name, ReadableMap data) {
|
||||
JitsiMeetView view = JitsiMeetView.getInstance();
|
||||
JitsiMeetViewListener listener
|
||||
= view != null ? view.getListener() : null;
|
||||
public void sendEvent(String name, ReadableMap data, String scope) {
|
||||
// The JavaScript App needs to provide uniquely identifying information
|
||||
// to the native ExternalAPI module so that the latter may match the
|
||||
// former to the native JitsiMeetView which hosts it.
|
||||
JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope);
|
||||
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
JitsiMeetViewListener listener = view.getListener();
|
||||
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Sigh, converting a ReadableMap to a HashMap is not supported
|
||||
// until React Native 0.46.
|
||||
final HashMap<String, Object> dataMap = new HashMap<>();
|
||||
// TODO Converting a ReadableMap to a HashMap is not supported until
|
||||
// React Native 0.46.
|
||||
HashMap<String, Object> dataMap = new HashMap<>();
|
||||
|
||||
switch (name) {
|
||||
case "CONFERENCE_FAILED":
|
||||
|
|
|
@ -29,8 +29,10 @@ RCT_EXPORT_MODULE();
|
|||
/**
|
||||
* Dispatches an event that occurred on JavaScript to the view's delegate.
|
||||
*
|
||||
* - name: name of the event.
|
||||
* - data: dictionary (JSON object in JS) with data associated with the event.
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
* by/associated with the specified {@code name}.
|
||||
* @param scope
|
||||
*/
|
||||
RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
data:(NSDictionary *)data
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import { Platform } from '../../base/react';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
|
@ -102,13 +101,8 @@ function _sendEvent(store: Object, name: string, data: Object) {
|
|||
if (app) {
|
||||
const { externalAPIScope } = app.props;
|
||||
|
||||
// TODO Lift the restriction on the JitsiMeetView instance count on
|
||||
// Android as well.
|
||||
if (externalAPIScope) {
|
||||
NativeModules.ExternalAPI.sendEvent(name, data, externalAPIScope);
|
||||
} else if (Platform.OS === 'android') {
|
||||
NativeModules.ExternalAPI.sendEvent(name, data);
|
||||
console.warn(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue