Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

W.I.P. feat(ios): add PiP functionality #15168

Closed
wants to merge 8 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(mobile/picture-in-picture): ios implementation
  • Loading branch information
Calinteodor committed Oct 22, 2024
commit 3561849fabd84dca9f08989ed07d873f87687af5
2 changes: 2 additions & 0 deletions react/features/app/types.ts
Original file line number Diff line number Diff line change
@@ -52,6 +52,7 @@ import { IBackgroundState } from '../mobile/background/reducer';
import { ICallIntegrationState } from '../mobile/call-integration/reducer';
import { IMobileExternalApiState } from '../mobile/external-api/reducer';
import { IFullScreenState } from '../mobile/full-screen/reducer';
import { IMobilePictureInPictureState } from '../mobile/picture-in-picture/reducer';
import { IMobileWatchOSState } from '../mobile/watchos/reducer';
import { INoAudioSignalState } from '../no-audio-signal/reducer';
import { INoiseDetectionState } from '../noise-detection/reducer';
@@ -142,6 +143,7 @@ export interface IReduxState {
'features/lobby': ILobbyState;
'features/mobile/audio-mode': IMobileAudioModeState;
'features/mobile/external-api': IMobileExternalApiState;
'features/mobile/picture-in-picture': IMobilePictureInPictureState;
'features/mobile/watchos': IMobileWatchOSState;
'features/no-audio-signal': INoAudioSignalState;
'features/noise-detection': INoiseDetectionState;
10 changes: 10 additions & 0 deletions react/features/base/media/components/native/FallbackView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { View } from 'react-native';

const FallbackView = () => {

return(
<View style={{ height: 800, width: 400, backgroundColor: 'red' }} />
)
}

export default FallbackView;
69 changes: 64 additions & 5 deletions react/features/base/media/components/native/Video.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import React, { Component } from 'react';
import React, {Component, RefObject} from 'react';
import { GestureResponderEvent } from 'react-native';
import { MediaStream, RTCView } from 'react-native-webrtc';
import { connect } from 'react-redux';

import { MediaStream, RTCPIPView, startIOSPIP, stopIOSPIP } from 'react-native-webrtc';

import { IReduxState} from '../../../../app/types';
import { translate } from '../../../i18n/functions';
import Pressable from '../../../react/components/native/Pressable';

import logger from '../../logger';

import VideoTransform from './VideoTransform';
import styles from './styles';
import FallbackView from "./FallbackView";


/**
* The type of the React {@code Component} props of {@link Video}.
*/
interface IProps {

_enableIosPIP?: boolean;

mirror: boolean;

onPlaying: Function;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ISSUE: @typescript-eslint/ban-types (Severity: Medium)
Don't use Function as a type. The Function type accepts any function-like value.
It provides no type safety when calling the function, which can be a common source of bugs.
It also accepts things like class declarations, which will throw at runtime as they will not be called with new.
If you are expecting the function to accept certain arguments, you should explicitly define the function shape.

Remediation:
I understand this is not part of the change but this is a good best practice recommendation from the automation. Please consider improving in the future

🤖 powered by PullRequest Automation 👋 verified by Xiaoyong W

@@ -58,7 +69,15 @@ interface IProps {
* {@code HTMLVideoElement} and wraps around react-native-webrtc's
* {@link RTCView}.
*/
export default class Video extends Component<IProps> {
class Video extends Component<IProps> {

_ref: RefObject<typeof Video>;

constructor(props) {
super(props);
this._ref = React.createRef();
}

/**
* React Component method that executes once component is mounted.
*
@@ -79,7 +98,28 @@ export default class Video extends Component<IProps> {
* @returns {ReactElement|null}
*/
render() {
const { onPress, stream, zoomEnabled } = this.props;
const { _enableIosPIP, onPress, stream, zoomEnabled } = this.props;

const iosPIPOptions = {
startAutomatically: true,
fallbackView: (<FallbackView />),
preferredSize: {
width: 400,
height: 800,
}
}

if (_enableIosPIP) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ISSUE: no-console (Severity: Medium)
Unexpected 'console' statement.

Remediation:
This is an automation generated comment regarding using console.log. It's a good recommendation to avoid console statement in production code. This includes several other console usages in this code

🤖 powered by PullRequest Automation 👋 verified by Xiaoyong W

console.log('TESTING _enableIosPIP is', _enableIosPIP);
console.log(this._ref?.current, 'Picture in picture mode on');
// logger.warn(this._ref?.current, `Picture in picture mode on`);
// startIOSPIP(this._ref?.current);
} else {
console.log('TESTING _enableIosPIP is', _enableIosPIP);
console.log('TESTING Picture in picture mode off');
// logger.warn(`Picture in picture mode off`);
// stopIOSPIP(this._ref?.current);
}

if (stream) {
// RTCView
@@ -90,9 +130,11 @@ export default class Video extends Component<IProps> {
: 'cover';
const rtcView
= (
<RTCView
<RTCPIPView
iosPIP = { iosPIPOptions }
mirror = { this.props.mirror }
objectFit = { objectFit }
ref = { this._ref }
streamURL = { stream.toURL() }
style = { style }
zOrder = { this.props.zOrder } />
@@ -132,3 +174,20 @@ export default class Video extends Component<IProps> {
return null;
}
}

/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Object}
*/
function _mapStateToProps(state: IReduxState) {
const iosPIP = state['features/mobile/picture-in-picture']?.enableIosPIP

return {
_enableIosPIP: iosPIP
};
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ISSUE: @typescript-eslint/ban-ts-comment (Severity: Medium)
Do not use "@ts-ignore" because it alters compilation errors.

Remediation:
This is a valid recommendation from automation to avoid using @ts-ignore

🤖 powered by PullRequest Automation 👋 verified by Xiaoyong W

// @ts-ignore
export default translate(connect(_mapStateToProps)(Video));
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ import TestHint from '../../testing/components/TestHint';
import { getVideoTrackByParticipant } from '../../tracks/functions';
import { ITrack } from '../../tracks/types';
import { getParticipantById, getParticipantDisplayName, isSharedVideoParticipant } from '../functions';
import { FakeParticipant } from '../types';

import styles from './styles';

@@ -71,7 +72,7 @@ interface IProps {
/**
* Whether video should be disabled for his view.
*/
disableVideo?: boolean;
disableVideo?: boolean | FakeParticipant;

/**
* Callback to invoke when the {@code ParticipantView} is clicked/pressed.
12 changes: 12 additions & 0 deletions react/features/mobile/picture-in-picture/actionTypes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/**
* The type of redux action to enable picture-in-picture for ios.
*
* {
* type: ENABLE_IOS_PIP,
* enableIosPIP: boolean
* }
*
* @public
*/
export const ENABLE_IOS_PIP = 'ENABLE_IOS_PIP';

/**
* The type of redux action to enter (or rather initiate entering)
* picture-in-picture.
9 changes: 7 additions & 2 deletions react/features/mobile/picture-in-picture/actions.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import { NativeModules } from 'react-native';
import { IStore } from '../../app/types';
import Platform from '../../base/react/Platform.native';

import { ENTER_PICTURE_IN_PICTURE } from './actionTypes';
import { ENABLE_IOS_PIP, ENTER_PICTURE_IN_PICTURE } from './actionTypes';
import { isPipEnabled } from './functions';
import logger from './logger';

@@ -24,8 +24,13 @@ export function enterPictureInPicture() {
// fine to enter PiP mode.
if (isPipEnabled(getState())) {
const { PictureInPicture } = NativeModules;
const { enableIosPIP } = getState()['features/mobile/picture-in-picture'];
const p
= Platform.OS === 'android'
= Platform.OS === 'ios'
? dispatch({
type: ENABLE_IOS_PIP,
enableIosPIP: !enableIosPIP })
: Platform.OS === 'android'
? PictureInPicture
? PictureInPicture.enterPictureInPicture()
: Promise.reject(
24 changes: 24 additions & 0 deletions react/features/mobile/picture-in-picture/middleware.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
import { APP_STATE_CHANGED } from '../background/actionTypes';
import { ENABLE_IOS_PIP } from './actionTypes';

MiddlewareRegistry.register(store => next => action => {
const result = next(action);

switch (action.type) {
case APP_STATE_CHANGED: {
const state = store.getState();
const { dispatch } = store;
const { appState } = state['features/background'];

if (appState === 'inactive') {
dispatch({
type: ENABLE_IOS_PIP,
enableIosPIP: false })
}
break;
}
}

return result;
})
29 changes: 29 additions & 0 deletions react/features/mobile/picture-in-picture/reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import ReducerRegistry from '../../base/redux/ReducerRegistry';
import { ENABLE_IOS_PIP } from './actionTypes';

const DEFAULT_STATE = {
enableIosPIP: false
};

export interface IMobilePictureInPictureState {
enableIosPIP: boolean;
}

const STORE_NAME = 'features/mobile/picture-in-picture';

ReducerRegistry.register<IMobilePictureInPictureState>(STORE_NAME, (state = DEFAULT_STATE, action): IMobilePictureInPictureState => {
switch (action.type) {

case ENABLE_IOS_PIP: {
const { enableIosPIP } = action;

return {
...state,
enableIosPIP
}
}

default:
return state;
}
})