[RN] Mute local video when app is in the background

This commit is contained in:
Saúl Ibarra Corretgé 2017-02-08 12:37:52 +01:00 committed by Lyubomir Marinov
parent c26f9cc01f
commit 4519f26adf
6 changed files with 275 additions and 0 deletions

View File

@ -4,6 +4,7 @@ import { Linking } from 'react-native';
import { Platform } from '../../base/react';
import '../../audio-mode';
import '../../background';
import '../../full-screen';
import '../../wake-lock';

View File

@ -0,0 +1,44 @@
import { Symbol } from '../base/react';
/**
* Action to set the AppState API change event listener.
*
* {
* type: _SET_APP_STATE_LISTENER,
* listener: Function
* }
*
* @private
*/
export const _SET_APP_STATE_LISTENER
= Symbol('_SET_APP_STATE_LISTENER');
/**
* Action to signal video will be muted because the app is going to the
* background.
*
* {
* type: _SET_BACKGROUND_VIDEO_MUTED,
* muted: boolean
* }
*
* @private
*/
export const _SET_BACKGROUND_VIDEO_MUTED
= Symbol('_SET_BACKGROUND_VIDEO_MUTED');
/**
* Action which signals that the App state has changed (in terms
* of execution mode).
*
* The application state can be one of 'active', 'inactive' or 'background',
* see: https://facebook.github.io/react-native/docs/appstate.html
*
* {
* type: APP_STATE_CHANGED,
* appState: string
* }
*
* @public
*/
export const APP_STATE_CHANGED = Symbol('APP_STATE_CHANGED');

View File

@ -0,0 +1,101 @@
import {
_SET_APP_STATE_LISTENER,
_SET_BACKGROUND_VIDEO_MUTED,
APP_STATE_CHANGED
} from './actionTypes';
import { setVideoMuted } from '../base/media';
/**
* Signals that the App state has changed (in terms of execution mode). The
* application can be in 3 states: 'active', 'inactive' and 'background'.
*
* @see https://facebook.github.io/react-native/docs/appstate.html
*
* @param {string} appState - The new App state.
* @returns {{
* type: APP_STATE_CHANGED,
* appState: string
* }}
*/
export function appStateChanged(appState: string) {
return {
type: APP_STATE_CHANGED,
appState
};
}
/**
* Signals that the app should mute video because it's now running in
* the background, or unmute it, if it came back from the background.
*
* If video was already muted nothing will happen, otherwise it will be
* muted. When coming back from the background the previous state will
* be restored.
*
* @param {boolean} muted - Set to true if video should be muted, false
* otherwise.
* @returns {Function}
*/
export function setBackgroundVideoMuted(muted: boolean) {
return (dispatch, getState) => {
if (muted) {
const mediaState = getState()['features/base/media'];
if (mediaState.video.muted) {
// Video is already muted, do nothing.
return;
}
} else {
const bgState = getState()['features/background'];
if (!bgState.videoMuted) {
// We didn't mute video, do nothing.
return;
}
}
dispatch(_setBackgroundVideoMuted(muted));
dispatch(setVideoMuted(muted));
};
}
/**
* Internal action which sets the listener to be used with React Native's
* AppState API.
*
* @param {Function} listener - Function to be set as the change event
* listener.
* @returns {{
* type: _SET_APP_STATE_LISTENER,
* listener: Function
* }}
*/
export function _setAppStateListener(listener: ?Function) {
return {
type: _SET_APP_STATE_LISTENER,
listener
};
}
/**
* Internal action which signals that video is going to be muted because the
* application is going to the background. This action is used to remember if
* video was muted due to the app going to the background vs user's choice.
*
* @param {type} muted - Set to true if video will be muted, false otherwise.
* @private
* @returns {{
* type: _SET_BACKGROUND_VIDEO_MUTED,
* muted: boolean
* }}
*/
function _setBackgroundVideoMuted(muted: boolean) {
return {
type: _SET_BACKGROUND_VIDEO_MUTED,
muted
};
}

View File

@ -0,0 +1,2 @@
import './middleware';
import './reducer';

View File

@ -0,0 +1,96 @@
/* @flow */
import { AppState } from 'react-native';
import type { Dispatch } from 'redux';
import {
_setAppStateListener,
appStateChanged,
setBackgroundVideoMuted
} from './actions';
import {
_SET_APP_STATE_LISTENER,
APP_STATE_CHANGED
} from './actionTypes';
import {
APP_WILL_MOUNT,
APP_WILL_UNMOUNT
} from '../app';
import { MiddlewareRegistry } from '../base/redux';
/**
* Middleware that captures App lifetime actions and subscribes to application
* state changes. When the application state changes it will fire the action
* requred to mute or unmute the local video in case the application goes to
* the backgound or comes back from it.
*
* @see https://facebook.github.io/react-native/docs/appstate.html
* @param {Store} store - Redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case _SET_APP_STATE_LISTENER: {
const bgState = store.getState()['features/background'];
if (bgState.appStateListener) {
AppState.removeEventListener('change', bgState.listener);
}
if (action.listener) {
AppState.addEventListener('change', action.listener);
}
break;
}
case APP_STATE_CHANGED:
_handleAppStateChange(store.dispatch, action.appState);
break;
case APP_WILL_MOUNT: {
const listener
= __onAppStateChanged.bind(undefined, store.dispatch);
store.dispatch(_setAppStateListener(listener));
break;
}
case APP_WILL_UNMOUNT:
store.dispatch(_setAppStateListener(null));
break;
}
return next(action);
});
/**
* Handler for app state changes. If will fire the necessary actions for
* local video to be muted when the app goes to the background, and to
* unmute it when it comes back.
*
* @param {Dispatch} dispatch - Redux dispatch function.
* @param {string} appState - Current app state.
* @private
* @returns {void}
*/
function _handleAppStateChange(dispatch: Dispatch<*>, appState: string) {
// XXX: we purposely don't handle the 'inactive' state.
if (appState === 'background') {
dispatch(setBackgroundVideoMuted(true));
} else if (appState === 'active') {
dispatch(setBackgroundVideoMuted(false));
}
}
/**
* Handler called by React's AppState API indicating that the application state
* has changed.
*
* @param {Dispatch} dispatch - Redux dispatch function.
* @param {string} appState - The current application execution state.
* @private
* @returns {void}
*/
function __onAppStateChanged(dispatch: Dispatch<*>, appState: string) {
dispatch(appStateChanged(appState));
}

View File

@ -0,0 +1,31 @@
import {
_SET_APP_STATE_LISTENER,
_SET_BACKGROUND_VIDEO_MUTED,
APP_STATE_CHANGED
} from './actionTypes';
import { ReducerRegistry } from '../base/redux';
ReducerRegistry.register('features/background', (state = {}, action) => {
switch (action.type) {
case _SET_APP_STATE_LISTENER:
return {
...state,
appStateListener: action.listener
};
case _SET_BACKGROUND_VIDEO_MUTED:
return {
...state,
videoMuted: action.muted
};
case APP_STATE_CHANGED:
return {
...state,
appState: action.appState
};
}
return state;
});