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) {
|
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() {
|
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
|
$RNVideoUseGoogleIMA=true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**If you are using Expo you can use [expo plugin](other/expo.md) for it**
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>Android</summary>
|
<summary>Android</summary>
|
||||||
@ -67,6 +69,8 @@ buildscript {
|
|||||||
|
|
||||||
### Enable custom feature in gradle file
|
### 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:
|
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)
|
- `useExoplayerIMA` - Enable Google IMA SDK (Ads support)
|
||||||
- `useExoplayerRtsp` - Enable RTSP support
|
- `useExoplayerRtsp` - Enable RTSP support
|
||||||
|
@ -3,5 +3,6 @@
|
|||||||
"misc": "Misc",
|
"misc": "Misc",
|
||||||
"debug": "Debugging",
|
"debug": "Debugging",
|
||||||
"new-arch": "New Architecture",
|
"new-arch": "New Architecture",
|
||||||
|
"expo": "Expo"
|
||||||
"plugin": "Plugin (experimental)"
|
"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"
|
"@types/react": "~18.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@expo/config-plugins": "^8.0.5",
|
||||||
"@jamesacarr/eslint-formatter-github-actions": "^0.2.0",
|
"@jamesacarr/eslint-formatter-github-actions": "^0.2.0",
|
||||||
"@react-native/eslint-config": "^0.72.2",
|
"@react-native/eslint-config": "^0.72.2",
|
||||||
"@release-it/conventional-changelog": "^7.0.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,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"lib": ["esnext"],
|
"lib": ["esnext"],
|
||||||
"module": "esnext",
|
"module": "CommonJS",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
@ -27,7 +27,8 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"verbatimModuleSyntax": true
|
"verbatimModuleSyntax": false,
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"exclude": ["examples", "lib", "docs"]
|
"exclude": ["examples", "lib", "docs"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user