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

feat: add expo plugins #3933

Merged
merged 12 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 4 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ buildscript {
}
}

// This looks funny but it's necessary to keep backwards compatibility (:
def safeExtGet(prop) {
return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : project.properties["RNVideo_" + prop]
return rootProject.ext.has(prop) ?
rootProject.ext.get(prop) : rootProject.ext.has("RNVideo_" + prop) ?
rootProject.ext.get("RNVideo_" + prop) : project.properties["RNVideo_" + prop]
}

def isNewArchitectureEnabled() {
Expand Down
1 change: 1 addition & 0 deletions app.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./lib/expo-plugins/withRNVideo');
4 changes: 4 additions & 0 deletions docs/pages/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ To enable google IMA usage define add following line in your podfile:
$RNVideoUseGoogleIMA=true
```

**If you are using Expo you can use [expo plugin](other/expo.md) for it**

</details>
<details>
<summary>Android</summary>
Expand All @@ -67,6 +69,8 @@ buildscript {

### Enable custom feature in gradle file

**If you are using Expo you can use [expo plugin](other/expo.md) for it**

You can disable or enable the following features by setting the following variables in your `android/build.gradle` file:
- `useExoplayerIMA` - Enable Google IMA SDK (Ads support)
- `useExoplayerRtsp` - Enable RTSP support
Expand Down
1 change: 1 addition & 0 deletions docs/pages/other/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"misc": "Misc",
"debug": "Debugging",
"new-arch": "New Architecture",
"expo": "Expo"
"plugin": "Plugin (experimental)"
}
40 changes: 40 additions & 0 deletions docs/pages/other/expo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Expo

## Expo plugin
From version `6.3.1`, we have added support for expo plugin. You can configure `react-native-video` properties in `app.json` (or `app.config.json` or `app.config.js`) file.
It's useful when you are using `expo` managed workflow (expo prebuild) as it will automatically configure `react-native-video` properties in native part of the expo project.

```json
// app.json
{
{
"name": "my app",
"plugins": [
[
"react-native-video",
{
// ...
"enableNotificationControls": true,
"androidExtensions": {
"useExoplayerRtsp": false,
"useExoplayerSmoothStreaming": false,
"useExoplayerHls": false,
"useExoplayerDash": false,
}
// ...
}
]
]
}
}
```

## Expo Plugin Properties

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| enableNotificationControls | boolean | false | Add required changes on android to use notification controls for video player |
| enableBackgroundAudio | boolean | false | Add required changes to play video in background on iOS |
| enableADSExtension | boolean | false | Add required changes to use ads extension for video player |
| enableCacheExtension | boolean | false | Add required changes to use cache extension for video player on iOS |
| androidExtensions | object | {} | You can enable/disable extensions as per your requirement - this allow to reduce library size on android |
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@types/react": "~18.0.0"
},
"devDependencies": {
"@expo/config-plugins": "^8.0.5",
"@jamesacarr/eslint-formatter-github-actions": "^0.2.0",
"@react-native/eslint-config": "^0.72.2",
"@release-it/conventional-changelog": "^7.0.2",
Expand Down
50 changes: 50 additions & 0 deletions src/expo-plugins/@types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export type ConfigProps = {
/**
* Whether to require permissions to be able to use notification controls.
* @default false
*/
enableNotificationControls?: boolean;
/**
* Whether to enable background audio feature.
* @default false
*/
enableBackgroundAudio?: boolean;
/**
* Whether to include ADS extension in the app (IMA SDK)
* @default false
* @see https://thewidlarzgroup.github.io/react-native-video/component/ads
*/
enableADSExtension?: boolean;
/**
* Whether to enable cache extension for ios in the app.
* @default false
* @see https://thewidlarzgroup.github.io/react-native-video/other/caching
*/
enableCacheExtension?: boolean;
/**
* Android extensions for ExoPlayer - you can choose which extensions to include in order to reduce the size of the app.
* @default { useExoplayerRtsp: false, useExoplayerSmoothStreaming: true, useExoplayerDash: true, useExoplayerHls: true }
*/
androidExtensions?: {
/**
* Whether to use ExoPlayer's RTSP extension.
* @default false
*/
useExoplayerRtsp?: boolean;
/**
* Whether to use ExoPlayer's SmoothStreaming extension.
* @default true
*/
useExoplayerSmoothStreaming?: boolean;
/**
* Whether to use ExoPlayer's Dash extension.
* @default true
*/
useExoplayerDash?: boolean;
/**
* Whether to use ExoPlayer's HLS extension.
* @default true
*/
useExoplayerHls?: boolean;
};
};
47 changes: 47 additions & 0 deletions src/expo-plugins/withAds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
withGradleProperties,
type ConfigPlugin,
withDangerousMod,
} from '@expo/config-plugins';
import {writeToPodfile} from './writeToPodfile';

/**
* Sets whether to enable the IMA SDK to use ADS with `react-native-video`.
*/
export const withAds: ConfigPlugin<boolean> = (c, enableADSExtension) => {
const android_key = 'RNVideo_useExoplayerIMA';
const ios_key = 'RNVideoUseGoogleIMA';

// -------------------- ANDROID --------------------
const configWithAndroid = withGradleProperties(c, (config) => {
config.modResults = config.modResults.filter((item) => {
if (item.type === 'property' && item.key === android_key) {
return false;
}
return true;
});

config.modResults.push({
type: 'property',
key: android_key,
value: enableADSExtension.toString(),
});

return config;
});

// -------------------- IOS --------------------
const complectedConfig = withDangerousMod(configWithAndroid, [
'ios',
(config) => {
writeToPodfile(
config.modRequest.projectRoot,
ios_key,
enableADSExtension.toString(),
);
return config;
},
]);

return complectedConfig;
};
53 changes: 53 additions & 0 deletions src/expo-plugins/withAndroidExtensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {withGradleProperties, type ConfigPlugin} from '@expo/config-plugins';
import type {ConfigProps} from './@types';

/**
* Sets the Android extensions for ExoPlayer in `gradle.properties`.
* You can choose which extensions to include in order to reduce the size of the app.
*/
export const withAndroidExtensions: ConfigPlugin<
ConfigProps['androidExtensions']
> = (c, androidExtensions) => {
const keys = [
'RNVideo_useExoplayerRtsp',
'RNVideo_useExoplayerSmoothStreaming',
'RNVideo_useExoplayerDash',
'RNVideo_useExoplayerHls',
];

if (!androidExtensions) {
androidExtensions = {
useExoplayerRtsp: false,
useExoplayerSmoothStreaming: true,
useExoplayerDash: true,
useExoplayerHls: true,
};
}

return withGradleProperties(c, (config) => {
config.modResults = config.modResults.filter((item) => {
if (item.type === 'property' && keys.includes(item.key)) {
return false;
}
return true;
});

for (const key of keys) {
const valueKey = key.replace(
'RNVideo_',
'',
) as keyof typeof androidExtensions;
const value = androidExtensions
? androidExtensions[valueKey] ?? false
: false;

config.modResults.push({
type: 'property',
key,
value: value.toString(),
});
}

return config;
});
};
26 changes: 26 additions & 0 deletions src/expo-plugins/withBackgroundAudio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {withInfoPlist, type ConfigPlugin} from '@expo/config-plugins';

/**
* Sets `UIBackgroundModes` in `Info.plist` to enable background audio on Apple platforms.
* This is required for audio to continue playing when the app is in the background.
*/
export const withBackgroundAudio: ConfigPlugin<boolean> = (
c,
enableBackgroundAudio,
) => {
return withInfoPlist(c, (config) => {
const modes = config.modResults.UIBackgroundModes || [];

if (enableBackgroundAudio) {
if (!modes.includes('audio')) {
modes.push('audio');
}
} else {
config.modResults.UIBackgroundModes = modes.filter(
(mode: string) => mode !== 'audio',
);
}

return config;
});
};
24 changes: 24 additions & 0 deletions src/expo-plugins/withCaching.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {type ConfigPlugin, withDangerousMod} from '@expo/config-plugins';
import {writeToPodfile} from './writeToPodfile';

/**
* Sets whether to include the cache dependency to use cache on iOS with `react-native-video`.
*/
export const withCaching: ConfigPlugin<boolean> = (
c,
enableCachingExtension,
) => {
const ios_key = 'RNVideoUseVideoCaching';

return withDangerousMod(c, [
'ios',
(config) => {
writeToPodfile(
config.modRequest.projectRoot,
ios_key,
enableCachingExtension.toString(),
);
return config;
},
]);
};
52 changes: 52 additions & 0 deletions src/expo-plugins/withNotificationControls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {withAndroidManifest, type ConfigPlugin} from '@expo/config-plugins';

export const withNotificationControls: ConfigPlugin<boolean> = (
c,
enableNotificationControls,
) => {
return withAndroidManifest(c, (config) => {
const manifest = config.modResults.manifest;

if (!enableNotificationControls) {
return config;
}

if (!manifest.application) {
console.warn(
'AndroidManifest.xml is missing an <application> element - skipping adding notification controls related config.',
);
return config;
}

// Add the service to the AndroidManifest.xml
manifest.application.map((application) => {
if (!application.service) {
application.service = [];
}

application.service.push({
$: {
'android:name': 'com.brentvatne.exoplayer.VideoPlaybackService',
'android:exported': 'false',
// @ts-expect-error: 'android:foregroundServiceType' does not exist in type 'ManifestServiceAttributes'.
'android:foregroundServiceType': 'mediaPlayback',
},
'intent-filter': [
{
action: [
{
$: {
'android:name': 'androidx.media3.session.MediaSessionService',
},
},
],
},
],
});

return application;
});

return config;
});
};
45 changes: 45 additions & 0 deletions src/expo-plugins/withRNVideo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {type ConfigPlugin, createRunOncePlugin} from '@expo/config-plugins';
import type {ConfigProps} from './@types';
import {withNotificationControls} from './withNotificationControls';
import {withAndroidExtensions} from './withAndroidExtensions';
import {withAds} from './withAds';
import {withBackgroundAudio} from './withBackgroundAudio';
import {withPermissions} from '@expo/config-plugins/build/android/Permissions';
import {withCaching} from './withCaching';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require('../../package.json');

const withRNVideo: ConfigPlugin<ConfigProps> = (config, props = {}) => {
const androidPermissions = [];

if (props.enableNotificationControls) {
config = withNotificationControls(config, props.enableNotificationControls);
androidPermissions.push('android.permission.FOREGROUND_SERVICE');
androidPermissions.push(
'android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK',
);
}

if (props.androidExtensions != null) {
config = withAndroidExtensions(config, props.androidExtensions);
}

if (props.enableADSExtension) {
config = withAds(config, props.enableADSExtension);
}

if (props.enableCacheExtension) {
config = withCaching(config, props.enableCacheExtension);
}

if (props.enableBackgroundAudio) {
config = withBackgroundAudio(config, props.enableBackgroundAudio);
}

config = withPermissions(config, androidPermissions);

return config;
};

export default createRunOncePlugin(withRNVideo, pkg.name, pkg.version);
Loading
Loading