feat(android) add screen-sharing support
Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org> Co-authored-by: zycwind <391321232@qq.com>
This commit is contained in:
parent
9742e90bb5
commit
9a35026d6a
|
@ -12,7 +12,7 @@
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:glEsVersion="0x00020000"
|
android:glEsVersion="0x00020000"
|
||||||
|
@ -34,8 +34,7 @@
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:supportsPictureInPicture="true"
|
android:supportsPictureInPicture="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize"></activity>
|
||||||
</activity>
|
|
||||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
@ -46,7 +45,9 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService" />
|
<service
|
||||||
|
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
|
||||||
|
android:foregroundServiceType="mediaProjection" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -43,6 +43,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||||
private static final String TAG = NAME;
|
private static final String TAG = NAME;
|
||||||
|
|
||||||
private static boolean isSupported;
|
private static boolean isSupported;
|
||||||
|
private boolean isDisabled;
|
||||||
|
|
||||||
public PictureInPictureModule(ReactApplicationContext reactContext) {
|
public PictureInPictureModule(ReactApplicationContext reactContext) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
|
@ -83,6 +84,10 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||||
*/
|
*/
|
||||||
@TargetApi(Build.VERSION_CODES.O)
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
public void enterPictureInPicture() {
|
public void enterPictureInPicture() {
|
||||||
|
if (isDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isSupported) {
|
if (!isSupported) {
|
||||||
throw new IllegalStateException("Picture-in-Picture not supported");
|
throw new IllegalStateException("Picture-in-Picture not supported");
|
||||||
}
|
}
|
||||||
|
@ -126,6 +131,11 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void setPictureInPictureDisabled(Boolean disabled) {
|
||||||
|
this.isDisabled = disabled;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isPictureInPictureSupported() {
|
public boolean isPictureInPictureSupported() {
|
||||||
return isSupported;
|
return isSupported;
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,8 +293,8 @@ PODS:
|
||||||
- React
|
- React
|
||||||
- react-native-splash-screen (3.2.0):
|
- react-native-splash-screen (3.2.0):
|
||||||
- React
|
- React
|
||||||
- react-native-webrtc (1.84.0):
|
- react-native-webrtc (1.84.1):
|
||||||
- React
|
- React-Core
|
||||||
- react-native-webview (10.9.0):
|
- react-native-webview (10.9.0):
|
||||||
- React
|
- React
|
||||||
- React-RCTActionSheet (0.61.5-jitsi.2):
|
- React-RCTActionSheet (0.61.5-jitsi.2):
|
||||||
|
@ -562,7 +562,7 @@ SPEC CHECKSUMS:
|
||||||
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||||
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
|
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
|
||||||
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
|
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
|
||||||
react-native-webrtc: 9268ae9a2bc9730796b0968d012327e92c392adf
|
react-native-webrtc: edd689b0d5a462d7a6f6f52bca3f9414fc0ee11c
|
||||||
react-native-webview: 6ee7868ca8eba635dbf7963986d1ab7959da0391
|
react-native-webview: 6ee7868ca8eba635dbf7963986d1ab7959da0391
|
||||||
React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
|
React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
|
||||||
React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6
|
React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6
|
||||||
|
|
|
@ -14265,9 +14265,9 @@
|
||||||
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
|
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
|
||||||
},
|
},
|
||||||
"react-native-webrtc": {
|
"react-native-webrtc": {
|
||||||
"version": "1.84.0",
|
"version": "1.84.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.84.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.84.1.tgz",
|
||||||
"integrity": "sha512-xPOFbrcehuBzLnFy3keCM2HyMsyCVDQjQNAn8SIHKH/PA8Q7kZ4spuytc2E1hBTr7zH/vQ2Px+DWqu7on12jag==",
|
"integrity": "sha512-ewZBgKE+YhLaivo9Wh6aiaEp8ZRvFMqblrkDl1nptQiNNH6CungoAzSOxGDnHWAxepRfiUrW5qnADrsYKmaNeQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"base64-js": "^1.1.2",
|
"base64-js": "^1.1.2",
|
||||||
"event-target-shim": "^1.0.5",
|
"event-target-shim": "^1.0.5",
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
"react-native-svg-transformer": "0.14.3",
|
"react-native-svg-transformer": "0.14.3",
|
||||||
"react-native-url-polyfill": "1.2.0",
|
"react-native-url-polyfill": "1.2.0",
|
||||||
"react-native-watch-connectivity": "0.4.3",
|
"react-native-watch-connectivity": "0.4.3",
|
||||||
"react-native-webrtc": "1.84.0",
|
"react-native-webrtc": "1.84.1",
|
||||||
"react-native-webview": "10.9.0",
|
"react-native-webview": "10.9.0",
|
||||||
"react-native-youtube-iframe": "1.2.3",
|
"react-native-youtube-iframe": "1.2.3",
|
||||||
"react-redux": "7.1.0",
|
"react-redux": "7.1.0",
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
"react-transition-group": "2.4.0",
|
"react-transition-group": "2.4.0",
|
||||||
"redux": "4.0.4",
|
"redux": "4.0.4",
|
||||||
"redux-thunk": "2.2.0",
|
"redux-thunk": "2.2.0",
|
||||||
"rnnoise-wasm": "github:jitsi/rnnoise-wasm.git#566a16885897704d6e6d67a1d5ac5d39781db2af",
|
"rnnoise-wasm": "github:jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af",
|
||||||
"rtcstats": "github:jitsi/rtcstats#v6.2.0",
|
"rtcstats": "github:jitsi/rtcstats#v6.2.0",
|
||||||
"stackblur-canvas": "2.3.0",
|
"stackblur-canvas": "2.3.0",
|
||||||
"styled-components": "3.4.9",
|
"styled-components": "3.4.9",
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { setPictureInPictureDisabled } from '../../mobile/picture-in-picture/functions';
|
||||||
|
import { setAudioOnly } from '../audio-only';
|
||||||
|
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||||
|
import { MiddlewareRegistry } from '../redux';
|
||||||
|
import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
|
||||||
|
import { destroyLocalDesktopTrackIfExists, replaceLocalTrack } from '../tracks/actions';
|
||||||
|
import { getLocalVideoTrack, isLocalVideoTrackDesktop } from '../tracks/functions';
|
||||||
|
|
||||||
|
import './middleware.any';
|
||||||
|
|
||||||
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
|
switch (action.type) {
|
||||||
|
case TOGGLE_SCREENSHARING: {
|
||||||
|
_toggleScreenSharing(store);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(action);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles screen sharing.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Store} store - The redux.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function _toggleScreenSharing(store) {
|
||||||
|
const { dispatch, getState } = store;
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
const isSharing = isLocalVideoTrackDesktop(state);
|
||||||
|
|
||||||
|
if (isSharing) {
|
||||||
|
dispatch(destroyLocalDesktopTrackIfExists());
|
||||||
|
} else {
|
||||||
|
_startScreenSharing(dispatch, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates desktop track and replaces the local one.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
|
||||||
|
* @param {Object} state - The redux state.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function _startScreenSharing(dispatch, state) {
|
||||||
|
setPictureInPictureDisabled(true);
|
||||||
|
|
||||||
|
JitsiMeetJS.createLocalTracks({ devices: [ 'desktop' ] })
|
||||||
|
.then(tracks => {
|
||||||
|
const track = tracks[0];
|
||||||
|
const currentLocalTrack = getLocalVideoTrack(state['features/base/tracks']);
|
||||||
|
const currentJitsiTrack = currentLocalTrack && currentLocalTrack.jitsiTrack;
|
||||||
|
|
||||||
|
dispatch(replaceLocalTrack(currentJitsiTrack, track));
|
||||||
|
|
||||||
|
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||||
|
|
||||||
|
if (audioOnly) {
|
||||||
|
dispatch(setAudioOnly(false));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log('ERROR creating ScreeSharing stream ', error);
|
||||||
|
|
||||||
|
setPictureInPictureDisabled(false);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import UIEvents from '../../../../service/UI/UIEvents';
|
||||||
|
import { MiddlewareRegistry } from '../redux';
|
||||||
|
import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
|
||||||
|
|
||||||
|
import './middleware.any';
|
||||||
|
|
||||||
|
declare var APP: Object;
|
||||||
|
|
||||||
|
MiddlewareRegistry.register((/* store */) => next => action => {
|
||||||
|
switch (action.type) {
|
||||||
|
case TOGGLE_SCREENSHARING: {
|
||||||
|
if (typeof APP === 'object') {
|
||||||
|
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(action);
|
||||||
|
});
|
|
@ -16,6 +16,7 @@ import {
|
||||||
getParticipantCount
|
getParticipantCount
|
||||||
} from '../participants/functions';
|
} from '../participants/functions';
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
|
import { isLocalVideoTrackDesktop } from '../tracks/functions';
|
||||||
|
|
||||||
import { limitLastN } from './functions';
|
import { limitLastN } from './functions';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
@ -78,7 +79,7 @@ function _updateLastN({ getState }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof appState !== 'undefined' && appState !== 'active') {
|
if (typeof appState !== 'undefined' && appState !== 'active') {
|
||||||
lastN = 0;
|
lastN = isLocalVideoTrackDesktop(state) ? 1 : 0;
|
||||||
} else if (audioOnly) {
|
} else if (audioOnly) {
|
||||||
const { screenShares, tileViewEnabled } = state['features/video-layout'];
|
const { screenShares, tileViewEnabled } = state['features/video-layout'];
|
||||||
const largeVideoParticipantId = state['features/large-video'].participantId;
|
const largeVideoParticipantId = state['features/large-video'].participantId;
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { isRoomValid, SET_ROOM } from '../conference';
|
||||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
import { getPropertyValue } from '../settings';
|
import { getPropertyValue } from '../settings';
|
||||||
import { setTrackMuted, TRACK_ADDED } from '../tracks';
|
import { isLocalVideoTrackDesktop, setTrackMuted, TRACK_ADDED } from '../tracks';
|
||||||
|
|
||||||
import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
|
import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
|
||||||
import {
|
import {
|
||||||
|
@ -73,13 +73,15 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
* @private
|
* @private
|
||||||
* @returns {Object} The value returned by {@code next(action)}.
|
* @returns {Object} The value returned by {@code next(action)}.
|
||||||
*/
|
*/
|
||||||
function _appStateChanged({ dispatch }, next, action) {
|
function _appStateChanged({ dispatch, getState }, next, action) {
|
||||||
|
if (navigator.product === 'ReactNative') {
|
||||||
const { appState } = action;
|
const { appState } = action;
|
||||||
const mute = appState !== 'active'; // Note that 'background' and 'inactive' are treated equal.
|
const mute = appState !== 'active' && !isLocalVideoTrackDesktop(getState());
|
||||||
|
|
||||||
sendAnalytics(createTrackMutedEvent('video', 'background mode', mute));
|
sendAnalytics(createTrackMutedEvent('video', 'background mode', mute));
|
||||||
|
|
||||||
dispatch(setVideoMuted(mute, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
|
dispatch(setVideoMuted(mute, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
|
||||||
|
}
|
||||||
|
|
||||||
return next(action);
|
return next(action);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,8 @@ import {
|
||||||
MEDIA_TYPE,
|
MEDIA_TYPE,
|
||||||
setAudioMuted,
|
setAudioMuted,
|
||||||
setVideoMuted,
|
setVideoMuted,
|
||||||
VIDEO_MUTISM_AUTHORITY
|
VIDEO_MUTISM_AUTHORITY,
|
||||||
|
VIDEO_TYPE
|
||||||
} from '../media';
|
} from '../media';
|
||||||
import { getLocalParticipant } from '../participants';
|
import { getLocalParticipant } from '../participants';
|
||||||
|
|
||||||
|
@ -24,7 +25,13 @@ import {
|
||||||
TRACK_UPDATED,
|
TRACK_UPDATED,
|
||||||
TRACK_WILL_CREATE
|
TRACK_WILL_CREATE
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import { createLocalTracksF, getLocalTrack, getLocalTracks, getTrackByJitsiTrack } from './functions';
|
import {
|
||||||
|
createLocalTracksF,
|
||||||
|
getLocalTrack,
|
||||||
|
getLocalTracks,
|
||||||
|
getLocalVideoTrack,
|
||||||
|
getTrackByJitsiTrack
|
||||||
|
} from './functions';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,6 +47,8 @@ export function createDesiredLocalTracks(...desiredTypes) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
|
dispatch(destroyLocalDesktopTrackIfExists());
|
||||||
|
|
||||||
if (desiredTypes.length === 0) {
|
if (desiredTypes.length === 0) {
|
||||||
const { audio, video } = state['features/base/media'];
|
const { audio, video } = state['features/base/media'];
|
||||||
|
|
||||||
|
@ -663,6 +672,22 @@ function _trackCreateCanceled(mediaType) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If thee local track if of type Desktop, it calls _disposeAndRemoveTracks) on it.
|
||||||
|
*
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export function destroyLocalDesktopTrackIfExists() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const videoTrack = getLocalVideoTrack(getState()['features/base/tracks']);
|
||||||
|
const isDesktopTrack = videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
|
||||||
|
|
||||||
|
if (isDesktopTrack) {
|
||||||
|
dispatch(_disposeAndRemoveTracks([ videoTrack.jitsiTrack ]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets UID of the displayed no data from source notification. Used to track
|
* Sets UID of the displayed no data from source notification. Used to track
|
||||||
* if the notification was previously displayed in this context.
|
* if the notification was previously displayed in this context.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* global APP */
|
/* global APP */
|
||||||
|
|
||||||
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||||
import { MEDIA_TYPE, setAudioMuted } from '../media';
|
import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
|
||||||
import {
|
import {
|
||||||
getUserSelectedCameraDeviceId,
|
getUserSelectedCameraDeviceId,
|
||||||
getUserSelectedMicDeviceId
|
getUserSelectedMicDeviceId
|
||||||
|
@ -383,6 +383,19 @@ export function isLocalTrackMuted(tracks, mediaType) {
|
||||||
return !track || track.muted;
|
return !track || track.muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the local video track is of type DESKtOP.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The redux state.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isLocalVideoTrackDesktop(state) {
|
||||||
|
const videoTrack = getLocalVideoTrack(state['features/base/tracks']);
|
||||||
|
|
||||||
|
return videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the remote track of the given media type and the given
|
* Returns true if the remote track of the given media type and the given
|
||||||
* participant is muted, false otherwise.
|
* participant is muted, false otherwise.
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { translate } from '../../../base/i18n';
|
||||||
import { IconMenuDown } from '../../../base/icons';
|
import { IconMenuDown } from '../../../base/icons';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||||
|
import { isLocalVideoTrackDesktop } from '../../../base/tracks/functions';
|
||||||
import { enterPictureInPicture } from '../actions';
|
import { enterPictureInPicture } from '../actions';
|
||||||
|
|
||||||
type Props = AbstractButtonProps & {
|
type Props = AbstractButtonProps & {
|
||||||
|
@ -63,7 +64,7 @@ class PictureInPictureButton extends AbstractButton<Props, *> {
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state): Object {
|
function _mapStateToProps(state): Object {
|
||||||
const flag = Boolean(getFeatureFlag(state, PIP_ENABLED));
|
const flag = Boolean(getFeatureFlag(state, PIP_ENABLED));
|
||||||
let enabled = flag;
|
let enabled = flag && !isLocalVideoTrackDesktop(state);
|
||||||
|
|
||||||
// Override flag for Android, since it might be unsupported.
|
// Override flag for Android, since it might be unsupported.
|
||||||
if (Platform.OS === 'android' && !NativeModules.PictureInPicture.SUPPORTED) {
|
if (Platform.OS === 'android' && !NativeModules.PictureInPicture.SUPPORTED) {
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { NativeModules } from 'react-native';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enabled/Disables the PictureInPicture mode in PiP native module.
|
||||||
|
*
|
||||||
|
* @param {boolean} disabled - Whether the PiP mode should be disabled.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export function setPictureInPictureDisabled(disabled: boolean) {
|
||||||
|
const { PictureInPicture } = NativeModules;
|
||||||
|
|
||||||
|
PictureInPicture.setPictureInPictureDisabled(disabled);
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './actions';
|
export * from './actions';
|
||||||
export * from './actionTypes';
|
export * from './actionTypes';
|
||||||
export * from './components';
|
export * from './components';
|
||||||
|
export * from './functions';
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {
|
||||||
sendAnalytics
|
sendAnalytics
|
||||||
} from '../../analytics';
|
} from '../../analytics';
|
||||||
import { setAudioOnly } from '../../base/audio-only';
|
import { setAudioOnly } from '../../base/audio-only';
|
||||||
import { hasAvailableDevices } from '../../base/devices';
|
|
||||||
import { translate } from '../../base/i18n';
|
import { translate } from '../../base/i18n';
|
||||||
import {
|
import {
|
||||||
VIDEO_MUTISM_AUTHORITY,
|
VIDEO_MUTISM_AUTHORITY,
|
||||||
|
@ -19,6 +18,7 @@ import { connect } from '../../base/redux';
|
||||||
import { AbstractVideoMuteButton } from '../../base/toolbox/components';
|
import { AbstractVideoMuteButton } from '../../base/toolbox/components';
|
||||||
import type { AbstractButtonProps } from '../../base/toolbox/components';
|
import type { AbstractButtonProps } from '../../base/toolbox/components';
|
||||||
import { getLocalVideoType, isLocalCameraTrackMuted } from '../../base/tracks';
|
import { getLocalVideoType, isLocalCameraTrackMuted } from '../../base/tracks';
|
||||||
|
import { isVideoMuteButtonDisabled } from '../functions';
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ function _mapStateToProps(state): Object {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_audioOnly: Boolean(audioOnly),
|
_audioOnly: Boolean(audioOnly),
|
||||||
_videoDisabled: !hasAvailableDevices(state, 'videoInput'),
|
_videoDisabled: isVideoMuteButtonDisabled(state),
|
||||||
_videoMediaType: getLocalVideoType(tracks),
|
_videoMediaType: getLocalVideoType(tracks),
|
||||||
_videoMuted: isLocalCameraTrackMuted(tracks)
|
_videoMuted: isLocalCameraTrackMuted(tracks)
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,6 +23,7 @@ import HelpButton from '../HelpButton';
|
||||||
import AudioOnlyButton from './AudioOnlyButton';
|
import AudioOnlyButton from './AudioOnlyButton';
|
||||||
import MoreOptionsButton from './MoreOptionsButton';
|
import MoreOptionsButton from './MoreOptionsButton';
|
||||||
import RaiseHandButton from './RaiseHandButton';
|
import RaiseHandButton from './RaiseHandButton';
|
||||||
|
import ScreenSharingButton from './ScreenSharingButton.js';
|
||||||
import ToggleCameraButton from './ToggleCameraButton';
|
import ToggleCameraButton from './ToggleCameraButton';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
|
@ -131,6 +132,7 @@ class OverflowMenu extends PureComponent<Props, State> {
|
||||||
<AudioOnlyButton { ...buttonProps } />
|
<AudioOnlyButton { ...buttonProps } />
|
||||||
<RaiseHandButton { ...buttonProps } />
|
<RaiseHandButton { ...buttonProps } />
|
||||||
<LobbyModeButton { ...buttonProps } />
|
<LobbyModeButton { ...buttonProps } />
|
||||||
|
<ScreenSharingButton { ...buttonProps } />
|
||||||
<MoreOptionsButton { ...moreOptionsButtonProps } />
|
<MoreOptionsButton { ...moreOptionsButtonProps } />
|
||||||
<Collapsible collapsed = { !showMore }>
|
<Collapsible collapsed = { !showMore }>
|
||||||
<ToggleCameraButton { ...buttonProps } />
|
<ToggleCameraButton { ...buttonProps } />
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
|
||||||
|
import { translate } from '../../../base/i18n';
|
||||||
|
import { IconShareDesktop } from '../../../base/icons';
|
||||||
|
import { connect } from '../../../base/redux';
|
||||||
|
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||||
|
import { toggleScreensharing, isLocalVideoTrackDesktop } from '../../../base/tracks';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the React {@code Component} props of {@link ScreenSharingButton}.
|
||||||
|
*/
|
||||||
|
type Props = AbstractButtonProps & {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether video is currently muted or not.
|
||||||
|
*/
|
||||||
|
_screensharing: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The redux {@code dispatch} function.
|
||||||
|
*/
|
||||||
|
dispatch: Function
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of a button for toggling screen sharing.
|
||||||
|
*/
|
||||||
|
class ScreenSharingButton extends AbstractButton<Props, *> {
|
||||||
|
accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen';
|
||||||
|
icon = IconShareDesktop;
|
||||||
|
label = 'toolbar.startScreenSharing';
|
||||||
|
toggledLabel = 'toolbar.stopScreenSharing';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles clicking / pressing the button.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
* @protected
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_handleClick() {
|
||||||
|
this.props.dispatch(toggleScreensharing());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether this button is in toggled state or not.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
* @protected
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
_isToggled() {
|
||||||
|
return this.props._screensharing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the redux state to the associated props for the
|
||||||
|
* {@code ToggleCameraButton} component.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _disabled: boolean,
|
||||||
|
* _screensharing: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state): Object {
|
||||||
|
return {
|
||||||
|
_screensharing: isLocalVideoTrackDesktop(state),
|
||||||
|
visible: Platform.OS === 'android'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect(_mapStateToProps)(ScreenSharingButton));
|
|
@ -1,7 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
import { hasAvailableDevices } from '../base/devices';
|
||||||
import { TOOLBOX_ALWAYS_VISIBLE, getFeatureFlag } from '../base/flags';
|
import { TOOLBOX_ALWAYS_VISIBLE, getFeatureFlag } from '../base/flags';
|
||||||
import { toState } from '../base/redux';
|
import { toState } from '../base/redux';
|
||||||
|
import { isLocalVideoTrackDesktop } from '../base/tracks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the toolbox is visible.
|
* Returns true if the toolbox is visible.
|
||||||
|
@ -18,3 +20,13 @@ export function isToolboxVisible(stateful: Object | Function) {
|
||||||
|
|
||||||
return enabled && (alwaysVisible || visible || participantCount === 1 || flag);
|
return enabled && (alwaysVisible || visible || participantCount === 1 || flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the video mute button is disabled or not.
|
||||||
|
*
|
||||||
|
* @param {string} state - The state from the Redux store.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isVideoMuteButtonDisabled(state: Object) {
|
||||||
|
return !hasAvailableDevices(state, 'videoInput') || isLocalVideoTrackDesktop(state);
|
||||||
|
}
|
||||||
|
|
|
@ -77,3 +77,13 @@ export function isAudioSettingsButtonDisabled(state: Object) {
|
||||||
export function isVideoSettingsButtonDisabled(state: Object) {
|
export function isVideoSettingsButtonDisabled(state: Object) {
|
||||||
return !hasAvailableDevices(state, 'videoInput');
|
return !hasAvailableDevices(state, 'videoInput');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the video mute button is disabled or not.
|
||||||
|
*
|
||||||
|
* @param {string} state - The state from the Redux store.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isVideoMuteButtonDisabled(state: Object) {
|
||||||
|
return !hasAvailableDevices(state, 'videoInput');
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { connect } from '../../base/redux';
|
||||||
import { ColorPalette } from '../../base/styles';
|
import { ColorPalette } from '../../base/styles';
|
||||||
import {
|
import {
|
||||||
createDesiredLocalTracks,
|
createDesiredLocalTracks,
|
||||||
|
destroyLocalDesktopTrackIfExists,
|
||||||
destroyLocalTracks
|
destroyLocalTracks
|
||||||
} from '../../base/tracks';
|
} from '../../base/tracks';
|
||||||
import { HelpView } from '../../help';
|
import { HelpView } from '../../help';
|
||||||
|
@ -81,6 +82,8 @@ class WelcomePage extends AbstractWelcomePage {
|
||||||
if (this.props._settings.startAudioOnly) {
|
if (this.props._settings.startAudioOnly) {
|
||||||
dispatch(destroyLocalTracks());
|
dispatch(destroyLocalTracks());
|
||||||
} else {
|
} else {
|
||||||
|
dispatch(destroyLocalDesktopTrackIfExists());
|
||||||
|
|
||||||
// Make sure we don't request the permission for the camera from
|
// Make sure we don't request the permission for the camera from
|
||||||
// the start. We will, however, create a video track iff the user
|
// the start. We will, however, create a video track iff the user
|
||||||
// already granted the permission.
|
// already granted the permission.
|
||||||
|
|
Loading…
Reference in New Issue