[Android] Allow multiple JitsiMeetViews

This commit is contained in:
Lyubo Marinov 2017-06-10 03:34:11 -05:00
parent 10e5e0fdf5
commit 4b2add7aa6
4 changed files with 166 additions and 157 deletions

View File

@ -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");
public static JitsiMeetView findViewByExternalAPIScope(
String externalAPIScope) {
for (JitsiMeetView view : views) {
if (view.externalAPIScope.equals(externalAPIScope)) {
return view;
}
}
/*
* 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());
}
}
/**
* 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,74 +71,24 @@ public class JitsiMeetView extends FrameLayout {
* @param application - <tt>Application</tt> instance which is running.
*/
private static void initReactInstanceManager(Application application) {
reactInstanceManager = ReactInstanceManager.builder()
.setApplication(application)
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
.addPackage(new com.facebook.react.shell.MainReactPackage())
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
.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.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;
reactInstanceManager
= ReactInstanceManager.builder()
.setApplication(application)
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
.addPackage(new com.facebook.react.shell.MainReactPackage())
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
.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.proximity.ProximityPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
}
/**
@ -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;
}
}

View File

@ -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":

View File

@ -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

View File

@ -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);
}
}
}