Skip to content

Commit

Permalink
feat: add expo plugins (TheWidlarzGroup#3933)
Browse files Browse the repository at this point in the history
* feat: add expo plugins

* add export

* fix import

* fix bugs

* build `lib` to `CommonJS`

* restore `build.gradle`

* remove plugin tmp

* add expo plugin for ios caching

* add docs for expo plugin

* fix expo plugin export

* fix docs
  • Loading branch information
KrzysztofMoch authored and moskalakamil committed Jul 17, 2024
1 parent e8363e5 commit f2234bb
Show file tree
Hide file tree
Showing 15 changed files with 378 additions and 3 deletions.
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

0 comments on commit f2234bb

Please sign in to comment.