feat: add expo plugins (#3933)
* 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
This commit is contained in:
parent
25c74e0534
commit
08f6caa645
@ -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() {
|
||||
|
1
app.plugin.js
Normal file
1
app.plugin.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./lib/expo-plugins/withRNVideo');
|
@ -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>
|
||||
@ -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
|
||||
|
@ -3,5 +3,6 @@
|
||||
"misc": "Misc",
|
||||
"debug": "Debugging",
|
||||
"new-arch": "New Architecture",
|
||||
"expo": "Expo"
|
||||
"plugin": "Plugin (experimental)"
|
||||
}
|
40
docs/pages/other/expo.md
Normal file
40
docs/pages/other/expo.md
Normal file
@ -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 |
|
@ -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",
|
||||
|
50
src/expo-plugins/@types.ts
Normal file
50
src/expo-plugins/@types.ts
Normal file
@ -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
src/expo-plugins/withAds.ts
Normal file
47
src/expo-plugins/withAds.ts
Normal file
@ -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
src/expo-plugins/withAndroidExtensions.ts
Normal file
53
src/expo-plugins/withAndroidExtensions.ts
Normal file
@ -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
src/expo-plugins/withBackgroundAudio.ts
Normal file
26
src/expo-plugins/withBackgroundAudio.ts
Normal file
@ -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
src/expo-plugins/withCaching.ts
Normal file
24
src/expo-plugins/withCaching.ts
Normal file
@ -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
src/expo-plugins/withNotificationControls.ts
Normal file
52
src/expo-plugins/withNotificationControls.ts
Normal file
@ -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
src/expo-plugins/withRNVideo.ts
Normal file
45
src/expo-plugins/withRNVideo.ts
Normal file
@ -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);
|
27
src/expo-plugins/writeToPodfile.ts
Normal file
27
src/expo-plugins/writeToPodfile.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import {mergeContents} from '@expo/config-plugins/build/utils/generateCode';
|
||||
|
||||
export const writeToPodfile = (
|
||||
projectRoot: string,
|
||||
key: string,
|
||||
value: string,
|
||||
) => {
|
||||
const podfilePath = path.join(projectRoot, 'ios', 'Podfile');
|
||||
const podfileContent = fs.readFileSync(podfilePath, 'utf8');
|
||||
|
||||
const newPodfileContent = mergeContents({
|
||||
tag: `rn-video-set-${key.toLowerCase()}`,
|
||||
src: podfileContent,
|
||||
newSrc: `$${key} = ${value}`,
|
||||
anchor: /platform :ios/,
|
||||
offset: 0,
|
||||
comment: '#',
|
||||
});
|
||||
|
||||
if (newPodfileContent.didMerge) {
|
||||
fs.writeFileSync(podfilePath, newPodfileContent.contents);
|
||||
} else {
|
||||
console.warn(`RNV - Failed to write "$${key} = ${value}" to Podfile`);
|
||||
}
|
||||
};
|
@ -14,7 +14,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"jsx": "react",
|
||||
"lib": ["esnext"],
|
||||
"module": "esnext",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
@ -27,7 +27,8 @@
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
"verbatimModuleSyntax": true
|
||||
"verbatimModuleSyntax": false,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": ["examples", "lib", "docs"]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user