[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 com.facebook.react.common.LifecycleState;
|
||||||
|
|
||||||
import java.net.URL;
|
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 {
|
public class JitsiMeetView extends FrameLayout {
|
||||||
/**
|
/**
|
||||||
|
@ -38,87 +42,24 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
*/
|
*/
|
||||||
private static final int BACKGROUND_COLOR = 0xFF111111;
|
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
|
* React Native bridge. The instance manager allows embedding applications
|
||||||
* to create multiple root views off the same JavaScript bundle.
|
* to create multiple root views off the same JavaScript bundle.
|
||||||
*/
|
*/
|
||||||
private static ReactInstanceManager reactInstanceManager;
|
private static ReactInstanceManager reactInstanceManager;
|
||||||
|
|
||||||
/**
|
private static final Set<JitsiMeetView> views
|
||||||
* {@link JitsiMeetViewListener} instance for reporting events occurring in
|
= Collections.newSetFromMap(new WeakHashMap<JitsiMeetView, Boolean>());
|
||||||
* Jitsi Meet.
|
|
||||||
*/
|
|
||||||
private JitsiMeetViewListener listener;
|
|
||||||
|
|
||||||
/**
|
public static JitsiMeetView findViewByExternalAPIScope(
|
||||||
* React Native root view.
|
String externalAPIScope) {
|
||||||
*/
|
for (JitsiMeetView view : views) {
|
||||||
private ReactRootView reactRootView;
|
if (view.externalAPIScope.equals(externalAPIScope)) {
|
||||||
|
return view;
|
||||||
/**
|
}
|
||||||
* 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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
return null;
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,74 +71,24 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
* @param application - <tt>Application</tt> instance which is running.
|
* @param application - <tt>Application</tt> instance which is running.
|
||||||
*/
|
*/
|
||||||
private static void initReactInstanceManager(Application application) {
|
private static void initReactInstanceManager(Application application) {
|
||||||
reactInstanceManager = ReactInstanceManager.builder()
|
reactInstanceManager
|
||||||
.setApplication(application)
|
= ReactInstanceManager.builder()
|
||||||
.setBundleAssetName("index.android.bundle")
|
.setApplication(application)
|
||||||
.setJSMainModuleName("index.android")
|
.setBundleAssetName("index.android.bundle")
|
||||||
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
|
.setJSMainModuleName("index.android")
|
||||||
.addPackage(new com.facebook.react.shell.MainReactPackage())
|
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
|
||||||
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
|
.addPackage(new com.facebook.react.shell.MainReactPackage())
|
||||||
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
|
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
|
||||||
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
|
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
|
||||||
.addPackage(new com.rnimmersive.RNImmersivePackage())
|
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
|
||||||
.addPackage(new org.jitsi.meet.sdk.audiomode.AudioModePackage())
|
.addPackage(new com.rnimmersive.RNImmersivePackage())
|
||||||
.addPackage(new org.jitsi.meet.sdk.externalapi.ExternalAPIPackage())
|
.addPackage(new org.jitsi.meet.sdk.audiomode.AudioModePackage())
|
||||||
.addPackage(new org.jitsi.meet.sdk.proximity.ProximityPackage())
|
.addPackage(
|
||||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
new org.jitsi.meet.sdk.externalapi.ExternalAPIPackage())
|
||||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
.addPackage(new org.jitsi.meet.sdk.proximity.ProximityPackage())
|
||||||
.build();
|
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -269,4 +160,117 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
reactInstanceManager.onNewIntent(intent);
|
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.
|
* Dispatches an event that occurred on JavaScript to the view's listener.
|
||||||
*
|
*
|
||||||
* @param name - Event name.
|
* @param name The name of the event.
|
||||||
* @param data - Ancillary data for the event.
|
* @param data The details/specifics of the event to send determined
|
||||||
|
* by/associated with the specified {@code name}.
|
||||||
|
* @param scope
|
||||||
*/
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void sendEvent(final String name, ReadableMap data) {
|
public void sendEvent(String name, ReadableMap data, String scope) {
|
||||||
JitsiMeetView view = JitsiMeetView.getInstance();
|
// The JavaScript App needs to provide uniquely identifying information
|
||||||
JitsiMeetViewListener listener
|
// to the native ExternalAPI module so that the latter may match the
|
||||||
= view != null ? view.getListener() : null;
|
// former to the native JitsiMeetView which hosts it.
|
||||||
|
JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope);
|
||||||
|
|
||||||
|
if (view == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JitsiMeetViewListener listener = view.getListener();
|
||||||
|
|
||||||
if (listener == null) {
|
if (listener == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Sigh, converting a ReadableMap to a HashMap is not supported
|
// TODO Converting a ReadableMap to a HashMap is not supported until
|
||||||
// until React Native 0.46.
|
// React Native 0.46.
|
||||||
final HashMap<String, Object> dataMap = new HashMap<>();
|
HashMap<String, Object> dataMap = new HashMap<>();
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "CONFERENCE_FAILED":
|
case "CONFERENCE_FAILED":
|
||||||
|
|
|
@ -29,8 +29,10 @@ RCT_EXPORT_MODULE();
|
||||||
/**
|
/**
|
||||||
* Dispatches an event that occurred on JavaScript to the view's delegate.
|
* Dispatches an event that occurred on JavaScript to the view's delegate.
|
||||||
*
|
*
|
||||||
* - name: name of the event.
|
* @param name The name of the event.
|
||||||
* - data: dictionary (JSON object in JS) with data associated with 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
|
RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||||
data:(NSDictionary *)data
|
data:(NSDictionary *)data
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { NativeModules } from 'react-native';
|
import { NativeModules } from 'react-native';
|
||||||
|
|
||||||
import { Platform } from '../../base/react';
|
|
||||||
import {
|
import {
|
||||||
CONFERENCE_FAILED,
|
CONFERENCE_FAILED,
|
||||||
CONFERENCE_JOINED,
|
CONFERENCE_JOINED,
|
||||||
|
@ -102,13 +101,8 @@ function _sendEvent(store: Object, name: string, data: Object) {
|
||||||
if (app) {
|
if (app) {
|
||||||
const { externalAPIScope } = app.props;
|
const { externalAPIScope } = app.props;
|
||||||
|
|
||||||
// TODO Lift the restriction on the JitsiMeetView instance count on
|
|
||||||
// Android as well.
|
|
||||||
if (externalAPIScope) {
|
if (externalAPIScope) {
|
||||||
NativeModules.ExternalAPI.sendEvent(name, data, 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