react-native-media-view 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Darkce
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # react-native-media-view
2
+
3
+ A high-performance React Native component for displaying images (including AVIF) and videos using native platform APIs. Renders images via `UIImageView` and videos via `AVKit` — no WebView overhead. Supports iOS only with the new architecture (Fabric).
4
+
5
+ ## Features
6
+
7
+ - **Native image rendering** — Uses `UIImageView` with `UIImage` for fast, memory-efficient display
8
+ - **AVIF support** — iOS 16+ decodes AVIF natively, including animated AVIF
9
+ - **Native video playback** — Uses `AVQueuePlayer` + `AVPlayerLayer` with seamless looping
10
+ - **Resize modes** — `contain`, `cover`, `stretch`, `center` (maps to `contentMode` / `videoGravity`)
11
+ - **Lifecycle events** — `onLoadStart`, `onLoad`, `onLoadEnd`, `onError`
12
+ - **No WebView, no third-party dependencies**
13
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ npm install react-native-media-view
18
+ ```
19
+
20
+ For iOS, install pods:
21
+
22
+ ```sh
23
+ cd ios && pod install
24
+ ```
25
+
26
+ ## Metro Configuration
27
+
28
+ Add `avif` to your Metro asset extensions in `metro.config.js`:
29
+
30
+ ```js
31
+ const { getDefaultConfig } = require('@react-native/metro-config');
32
+
33
+ const config = getDefaultConfig(__dirname);
34
+ config.resolver.assetExts.push('avif');
35
+
36
+ module.exports = config;
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```tsx
42
+ import { MediaView } from 'react-native-media-view';
43
+
44
+ // Local AVIF image
45
+ <MediaView
46
+ source={require('./assets/image.avif')}
47
+ resizeMode="contain"
48
+ style={{ width: 300, height: 300 }}
49
+ onLoad={() => console.log('Loaded')}
50
+ onError={(e) => console.error(e.nativeEvent.error)}
51
+ />
52
+
53
+ // Remote image
54
+ <MediaView
55
+ source={{ uri: 'https://example.com/photo.avif' }}
56
+ resizeMode="cover"
57
+ style={{ width: '100%', aspectRatio: 16 / 9 }}
58
+ />
59
+
60
+ // Video (auto-detected by file extension, loops + muted)
61
+ <MediaView
62
+ source={{ uri: 'https://example.com/clip.mp4' }}
63
+ resizeMode="cover"
64
+ style={{ width: '100%', height: 200 }}
65
+ />
66
+ ```
67
+
68
+ ## Props
69
+
70
+ | Prop | Type | Default | Description |
71
+ | ------------ | ----------------------------------------------- | ------------ | ------------------------------------------ |
72
+ | `source` | `ImageRequireSource \| { uri: string }` | **required** | Image or video source (`require()` or URI) |
73
+ | `resizeMode` | `'contain' \| 'cover' \| 'stretch' \| 'center'` | `'contain'` | How media fits the view |
74
+ | `style` | `ViewStyle` | — | Standard React Native view style |
75
+
76
+ ## Events
77
+
78
+ | Event | Payload | Description |
79
+ | ------------- | ----------- | ------------------------------------- |
80
+ | `onLoadStart` | — | Loading has begun |
81
+ | `onLoad` | — | Media is ready for display / playback |
82
+ | `onLoadEnd` | — | Loading finished (success or failure) |
83
+ | `onError` | `{ error }` | An error occurred |
84
+
85
+ ## How It Works
86
+
87
+ | Media type | Rendered with |
88
+ | ---------- | --------------------------------- |
89
+ | Image | `UIImageView` (`UIImage(data:)`) |
90
+ | Video | `AVQueuePlayer` + `AVPlayerLayer` |
91
+
92
+ - **Images** are loaded asynchronously (file or network) and decoded via `UIImage`, which supports AVIF on iOS 16+.
93
+ - **Videos** are detected by file extension (`.mp4`, `.mov`, `.webm`, etc.) and played with `AVQueuePlayer` + `AVPlayerLooper` for seamless looping. Playback is muted by default.
94
+
95
+ ## Requirements
96
+
97
+ - iOS 16.0+
98
+ - React Native 0.74+ (New Architecture / Fabric)
99
+
100
+ ## Contributing
101
+
102
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
103
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
104
+ - [Code of conduct](CODE_OF_CONDUCT.md)
105
+
106
+ ## License
107
+
108
+ MIT
109
+
110
+ ---
111
+
112
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,67 @@
1
+ buildscript {
2
+ ext.MediaView = [
3
+ kotlinVersion: "2.0.21",
4
+ minSdkVersion: 24,
5
+ compileSdkVersion: 36,
6
+ targetSdkVersion: 36
7
+ ]
8
+
9
+ ext.getExtOrDefault = { prop ->
10
+ if (rootProject.ext.has(prop)) {
11
+ return rootProject.ext.get(prop)
12
+ }
13
+
14
+ return MediaView[prop]
15
+ }
16
+
17
+ repositories {
18
+ google()
19
+ mavenCentral()
20
+ }
21
+
22
+ dependencies {
23
+ classpath "com.android.tools.build:gradle:8.7.2"
24
+ // noinspection DifferentKotlinGradleVersion
25
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
26
+ }
27
+ }
28
+
29
+
30
+ apply plugin: "com.android.library"
31
+ apply plugin: "kotlin-android"
32
+
33
+ apply plugin: "com.facebook.react"
34
+
35
+ android {
36
+ namespace "com.mediaview"
37
+
38
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
39
+
40
+ defaultConfig {
41
+ minSdkVersion getExtOrDefault("minSdkVersion")
42
+ targetSdkVersion getExtOrDefault("targetSdkVersion")
43
+ }
44
+
45
+ buildFeatures {
46
+ buildConfig true
47
+ }
48
+
49
+ buildTypes {
50
+ release {
51
+ minifyEnabled false
52
+ }
53
+ }
54
+
55
+ lint {
56
+ disable "GradleCompatible"
57
+ }
58
+
59
+ compileOptions {
60
+ sourceCompatibility JavaVersion.VERSION_1_8
61
+ targetCompatibility JavaVersion.VERSION_1_8
62
+ }
63
+ }
64
+
65
+ dependencies {
66
+ implementation "com.facebook.react:react-android"
67
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,15 @@
1
+ package com.mediaview
2
+
3
+ import android.content.Context
4
+ import android.util.AttributeSet
5
+ import android.view.View
6
+
7
+ class MediaView : View {
8
+ constructor(context: Context?) : super(context)
9
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
10
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
11
+ context,
12
+ attrs,
13
+ defStyleAttr
14
+ )
15
+ }
@@ -0,0 +1,41 @@
1
+ package com.mediaview
2
+
3
+ import android.graphics.Color
4
+ import com.facebook.react.module.annotations.ReactModule
5
+ import com.facebook.react.uimanager.SimpleViewManager
6
+ import com.facebook.react.uimanager.ThemedReactContext
7
+ import com.facebook.react.uimanager.ViewManagerDelegate
8
+ import com.facebook.react.uimanager.annotations.ReactProp
9
+ import com.facebook.react.viewmanagers.MediaViewManagerInterface
10
+ import com.facebook.react.viewmanagers.MediaViewManagerDelegate
11
+
12
+ @ReactModule(name = MediaViewManager.NAME)
13
+ class MediaViewManager : SimpleViewManager<MediaView>(),
14
+ MediaViewManagerInterface<MediaView> {
15
+ private val mDelegate: ViewManagerDelegate<MediaView>
16
+
17
+ init {
18
+ mDelegate = MediaViewManagerDelegate(this)
19
+ }
20
+
21
+ override fun getDelegate(): ViewManagerDelegate<MediaView>? {
22
+ return mDelegate
23
+ }
24
+
25
+ override fun getName(): String {
26
+ return NAME
27
+ }
28
+
29
+ public override fun createViewInstance(context: ThemedReactContext): MediaView {
30
+ return MediaView(context)
31
+ }
32
+
33
+ @ReactProp(name = "color")
34
+ override fun setColor(view: MediaView?, color: Int?) {
35
+ view?.setBackgroundColor(color ?: Color.TRANSPARENT)
36
+ }
37
+
38
+ companion object {
39
+ const val NAME = "MediaView"
40
+ }
41
+ }
@@ -0,0 +1,17 @@
1
+ package com.mediaview
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfoProvider
7
+ import com.facebook.react.uimanager.ViewManager
8
+
9
+ class MediaViewPackage : BaseReactPackage() {
10
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
11
+ return listOf(MediaViewManager())
12
+ }
13
+
14
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
15
+
16
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { emptyMap() }
17
+ }
@@ -0,0 +1,293 @@
1
+ //
2
+ // MediaImageView.swift
3
+ // react-native-media-view
4
+ //
5
+ // Native media view using UIImageView (for images) and AVKit (for video)
6
+ //
7
+
8
+ import AVFoundation
9
+ import AVKit
10
+ import Foundation
11
+ import UIKit
12
+
13
+ /// Delegate protocol for handling media view events
14
+ @objc public protocol MediaImageViewDelegate: AnyObject {
15
+ func handleOnLoadStart()
16
+ func handleOnLoad()
17
+ func handleOnLoadEnd()
18
+ func handleOnError(error: String)
19
+ }
20
+
21
+ /// Native UIView subclass for displaying images and video content
22
+ @objcMembers
23
+ public class MediaImageViewCore: UIView {
24
+ public weak var delegate: MediaImageViewDelegate?
25
+ private var currentURI: String?
26
+ private var currentResizeMode: String = "contain"
27
+
28
+ // MARK: - Image display
29
+
30
+ private lazy var imageView: UIImageView = {
31
+ let iv = UIImageView()
32
+ iv.clipsToBounds = true
33
+ iv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
34
+ iv.backgroundColor = .clear
35
+ return iv
36
+ }()
37
+
38
+ // MARK: - Video display
39
+
40
+ private var queuePlayer: AVQueuePlayer?
41
+ private var playerLayer: AVPlayerLayer?
42
+ private var playerLooper: AVPlayerLooper?
43
+ private var playerItem: AVPlayerItem?
44
+ private var statusObservation: NSKeyValueObservation?
45
+ private var loadingTask: URLSessionDataTask?
46
+
47
+ // MARK: - Init
48
+
49
+ public override init(frame: CGRect) {
50
+ super.init(frame: frame)
51
+ setupView()
52
+ }
53
+
54
+ public required init?(coder: NSCoder) {
55
+ super.init(coder: coder)
56
+ setupView()
57
+ }
58
+
59
+ private func setupView() {
60
+ clipsToBounds = true
61
+ backgroundColor = .clear
62
+ isUserInteractionEnabled = false
63
+ imageView.frame = bounds
64
+ addSubview(imageView)
65
+ }
66
+
67
+ // MARK: - Layout
68
+
69
+ public override func layoutSubviews() {
70
+ super.layoutSubviews()
71
+ imageView.frame = bounds
72
+ playerLayer?.frame = bounds
73
+ }
74
+
75
+ // MARK: - Video detection
76
+
77
+ private static let videoExtensions: Set<String> = [
78
+ "mp4", "webm", "mov", "m4v", "avi", "mkv", "ogg", "ogv",
79
+ ]
80
+
81
+ private func isVideoURL(_ urlString: String) -> Bool {
82
+ guard let url = URL(string: urlString) else { return false }
83
+ let ext = url.pathExtension.lowercased()
84
+ return Self.videoExtensions.contains(ext)
85
+ }
86
+
87
+ // MARK: - Public API
88
+
89
+ public func setSource(_ source: NSDictionary) {
90
+ guard let uri = source["uri"] as? String, !uri.isEmpty else {
91
+ return
92
+ }
93
+
94
+ // Avoid reloading the same URI
95
+ if uri == currentURI { return }
96
+
97
+ delegate?.handleOnLoadStart()
98
+ currentURI = uri
99
+
100
+ // Clean up previous content
101
+ cleanupVideo()
102
+ cancelImageLoading()
103
+ imageView.image = nil
104
+
105
+ if isVideoURL(uri) {
106
+ loadVideo(uri: uri)
107
+ } else {
108
+ loadImage(uri: uri)
109
+ }
110
+ }
111
+
112
+ public func setResizeMode(_ resizeMode: String?) {
113
+ guard let resizeMode, resizeMode != currentResizeMode else { return }
114
+ currentResizeMode = resizeMode
115
+ applyResizeMode()
116
+ }
117
+
118
+ // MARK: - Image Loading
119
+
120
+ private func loadImage(uri: String) {
121
+ imageView.isHidden = false
122
+
123
+ guard let url = URL(string: uri) else {
124
+ delegate?.handleOnError(error: "Invalid image URI")
125
+ delegate?.handleOnLoadEnd()
126
+ return
127
+ }
128
+
129
+ if url.isFileURL {
130
+ loadImageFromFile(url: url)
131
+ } else {
132
+ loadImageFromNetwork(url: url)
133
+ }
134
+ }
135
+
136
+ private func loadImageFromFile(url: URL) {
137
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
138
+ guard let self else { return }
139
+ do {
140
+ let data = try Data(contentsOf: url)
141
+ guard let image = UIImage(data: data) else {
142
+ DispatchQueue.main.async {
143
+ self.delegate?.handleOnError(error: "Failed to decode image")
144
+ self.delegate?.handleOnLoadEnd()
145
+ }
146
+ return
147
+ }
148
+ DispatchQueue.main.async {
149
+ guard self.currentURI == url.absoluteString else { return }
150
+ self.imageView.image = image
151
+ self.applyResizeMode()
152
+ self.delegate?.handleOnLoad()
153
+ self.delegate?.handleOnLoadEnd()
154
+ }
155
+ } catch {
156
+ DispatchQueue.main.async {
157
+ self.delegate?.handleOnError(error: error.localizedDescription)
158
+ self.delegate?.handleOnLoadEnd()
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ private func loadImageFromNetwork(url: URL) {
165
+ let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
166
+ guard let self else { return }
167
+ DispatchQueue.main.async {
168
+ guard self.currentURI == url.absoluteString else { return }
169
+
170
+ if let error {
171
+ self.delegate?.handleOnError(error: error.localizedDescription)
172
+ self.delegate?.handleOnLoadEnd()
173
+ return
174
+ }
175
+
176
+ guard let data, let image = UIImage(data: data) else {
177
+ self.delegate?.handleOnError(error: "Failed to decode image from network")
178
+ self.delegate?.handleOnLoadEnd()
179
+ return
180
+ }
181
+
182
+ self.imageView.image = image
183
+ self.applyResizeMode()
184
+ self.delegate?.handleOnLoad()
185
+ self.delegate?.handleOnLoadEnd()
186
+ }
187
+ }
188
+ loadingTask = task
189
+ task.resume()
190
+ }
191
+
192
+ private func cancelImageLoading() {
193
+ loadingTask?.cancel()
194
+ loadingTask = nil
195
+ }
196
+
197
+ // MARK: - Video Loading
198
+
199
+ private func loadVideo(uri: String) {
200
+ imageView.isHidden = true
201
+
202
+ guard let url = URL(string: uri) else {
203
+ delegate?.handleOnError(error: "Invalid video URI")
204
+ delegate?.handleOnLoadEnd()
205
+ return
206
+ }
207
+
208
+ let item = AVPlayerItem(url: url)
209
+ playerItem = item
210
+
211
+ let player = AVQueuePlayer()
212
+ queuePlayer = player
213
+ player.isMuted = true
214
+
215
+ // Loop playback indefinitely — looper manages item insertion
216
+ playerLooper = AVPlayerLooper(player: player, templateItem: item)
217
+
218
+ let pLayer = AVPlayerLayer(player: player)
219
+ pLayer.frame = bounds
220
+ playerLayer = pLayer
221
+ layer.addSublayer(pLayer)
222
+ applyVideoGravity()
223
+
224
+ // Observe the player's currentItem status for readiness
225
+ statusObservation = player.observe(\.currentItem?.status, options: [.new]) {
226
+ [weak self] observedPlayer, _ in
227
+ DispatchQueue.main.async {
228
+ guard let self else { return }
229
+ guard let currentItem = observedPlayer.currentItem else { return }
230
+ switch currentItem.status {
231
+ case .readyToPlay:
232
+ self.delegate?.handleOnLoad()
233
+ self.delegate?.handleOnLoadEnd()
234
+ observedPlayer.play()
235
+ case .failed:
236
+ let msg = currentItem.error?.localizedDescription ?? "Unknown video error"
237
+ self.delegate?.handleOnError(error: msg)
238
+ self.delegate?.handleOnLoadEnd()
239
+ default:
240
+ break
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ private func cleanupVideo() {
247
+ statusObservation?.invalidate()
248
+ statusObservation = nil
249
+ queuePlayer?.pause()
250
+ queuePlayer = nil
251
+ playerLooper = nil
252
+ playerItem = nil
253
+ playerLayer?.removeFromSuperlayer()
254
+ playerLayer = nil
255
+ }
256
+
257
+ // MARK: - Resize Mode
258
+
259
+ private func applyResizeMode() {
260
+ switch currentResizeMode {
261
+ case "cover":
262
+ imageView.contentMode = .scaleAspectFill
263
+ case "contain":
264
+ imageView.contentMode = .scaleAspectFit
265
+ case "stretch":
266
+ imageView.contentMode = .scaleToFill
267
+ case "center":
268
+ imageView.contentMode = .center
269
+ default:
270
+ imageView.contentMode = .scaleAspectFit
271
+ }
272
+ applyVideoGravity()
273
+ }
274
+
275
+ private func applyVideoGravity() {
276
+ guard let playerLayer else { return }
277
+ switch currentResizeMode {
278
+ case "cover":
279
+ playerLayer.videoGravity = .resizeAspectFill
280
+ case "stretch":
281
+ playerLayer.videoGravity = .resize
282
+ default:
283
+ playerLayer.videoGravity = .resizeAspect
284
+ }
285
+ }
286
+
287
+ // MARK: - Cleanup
288
+
289
+ deinit {
290
+ cleanupVideo()
291
+ cancelImageLoading()
292
+ }
293
+ }
@@ -0,0 +1,15 @@
1
+ #import <React/RCTViewComponentView.h>
2
+ #import <UIKit/UIKit.h>
3
+
4
+ #ifndef MediaViewNativeComponent_h
5
+ #define MediaViewNativeComponent_h
6
+
7
+ NS_ASSUME_NONNULL_BEGIN
8
+
9
+ @interface MediaView : RCTViewComponentView
10
+
11
+ @end
12
+
13
+ NS_ASSUME_NONNULL_END
14
+
15
+ #endif /* MediaViewNativeComponent_h */
@@ -0,0 +1,105 @@
1
+ //
2
+ // MediaView.mm
3
+ // react-native-media-view
4
+ //
5
+ // React Native Fabric component binding for media view
6
+ //
7
+
8
+ #import "MediaView.h"
9
+ #if __has_include(<react_native_media_view/react_native_media_view-Swift.h>)
10
+ #import <react_native_media_view/react_native_media_view-Swift.h>
11
+ #else
12
+ #import "react_native_media_view-Swift.h"
13
+ #endif
14
+ #import <React/RCTConversions.h>
15
+
16
+ #import <react/renderer/components/MediaViewSpec/ComponentDescriptors.h>
17
+ #import <react/renderer/components/MediaViewSpec/EventEmitters.h>
18
+ #import <react/renderer/components/MediaViewSpec/Props.h>
19
+ #import <react/renderer/components/MediaViewSpec/RCTComponentViewHelpers.h>
20
+
21
+ #import "RCTFabricComponentsPlugins.h"
22
+
23
+ using namespace facebook::react;
24
+
25
+ @interface MediaView () <MediaImageViewDelegate, RCTMediaViewViewProtocol>
26
+ @end
27
+
28
+ @implementation MediaView {
29
+ MediaImageViewCore *_view;
30
+ }
31
+
32
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
33
+ return concreteComponentDescriptorProvider<MediaViewComponentDescriptor>();
34
+ }
35
+
36
+ - (instancetype)initWithFrame:(CGRect)frame {
37
+ if (self = [super initWithFrame:frame]) {
38
+ static const auto defaultProps = std::make_shared<const MediaViewProps>();
39
+ _props = defaultProps;
40
+
41
+ _view = [[MediaImageViewCore alloc] init];
42
+ _view.delegate = self;
43
+
44
+ self.contentView = _view;
45
+ }
46
+
47
+ return self;
48
+ }
49
+
50
+ - (void)updateProps:(Props::Shared const &)props
51
+ oldProps:(Props::Shared const &)oldProps {
52
+ const auto &oldViewProps =
53
+ *std::static_pointer_cast<MediaViewProps const>(_props);
54
+ const auto &newViewProps =
55
+ *std::static_pointer_cast<MediaViewProps const>(props);
56
+
57
+ // Update source
58
+ if (oldViewProps.source.uri != newViewProps.source.uri) {
59
+ NSDictionary *sourceDict =
60
+ @{@"uri" : RCTNSStringFromString(newViewProps.source.uri)};
61
+ [_view setSource:sourceDict];
62
+ }
63
+
64
+ // Update resizeMode (aligned with React Native Image)
65
+ if (oldViewProps.resizeMode != newViewProps.resizeMode) {
66
+ [_view setResizeMode:RCTNSStringFromString(newViewProps.resizeMode)];
67
+ }
68
+
69
+ [super updateProps:props oldProps:oldProps];
70
+ }
71
+
72
+ #pragma mark - MediaImageViewDelegate
73
+
74
+ - (void)handleOnLoadStart {
75
+ if (_eventEmitter != nil) {
76
+ std::dynamic_pointer_cast<const MediaViewEventEmitter>(_eventEmitter)
77
+ ->onLoadStart(MediaViewEventEmitter::OnLoadStart{});
78
+ }
79
+ }
80
+
81
+ - (void)handleOnLoad {
82
+ if (_eventEmitter != nil) {
83
+ std::dynamic_pointer_cast<const MediaViewEventEmitter>(_eventEmitter)
84
+ ->onLoad(MediaViewEventEmitter::OnLoad{});
85
+ }
86
+ }
87
+
88
+ - (void)handleOnLoadEnd {
89
+ if (_eventEmitter != nil) {
90
+ std::dynamic_pointer_cast<const MediaViewEventEmitter>(_eventEmitter)
91
+ ->onLoadEnd(MediaViewEventEmitter::OnLoadEnd{});
92
+ }
93
+ }
94
+
95
+ - (void)handleOnErrorWithError:(NSString *)error {
96
+ if (_eventEmitter != nil) {
97
+ std::dynamic_pointer_cast<const MediaViewEventEmitter>(_eventEmitter)
98
+ ->onError(MediaViewEventEmitter::OnError{
99
+ .error = std::string([error UTF8String])});
100
+ }
101
+ }
102
+
103
+ @end
104
+
105
+ Class<RCTComponentViewProtocol> MediaViewCls(void) { return MediaView.class; }
@@ -0,0 +1,41 @@
1
+ import { codegenNativeComponent } from 'react-native';
2
+ import type { ViewProps } from 'react-native';
3
+ import type { DirectEventHandler } from 'react-native/Libraries/Types/CodegenTypes';
4
+ import type { HostComponent } from 'react-native';
5
+
6
+ /**
7
+ * Source props for media (resolved from require())
8
+ */
9
+ export interface MediaSourceProps {
10
+ /** URI of the image or video */
11
+ uri?: string;
12
+ }
13
+
14
+ /**
15
+ * Resize mode for image display (aligned with React Native Image)
16
+ */
17
+ export type ResizeMode = 'cover' | 'contain' | 'stretch' | 'center';
18
+
19
+ /**
20
+ * Native props for MediaView component
21
+ */
22
+ interface NativeProps extends ViewProps {
23
+ /** Source of the media */
24
+ source?: MediaSourceProps;
25
+ /** Resize mode for image display (aligned with React Native Image) */
26
+ resizeMode?: string;
27
+ /** Callback when loading starts */
28
+ onLoadStart?: DirectEventHandler<null>;
29
+ /** Callback when the image is loaded */
30
+ onLoad?: DirectEventHandler<null>;
31
+ /** Callback when loading ends (success or failure) */
32
+ onLoadEnd?: DirectEventHandler<null>;
33
+ /** Callback when an error occurs */
34
+ onError?: DirectEventHandler<Readonly<{ error: string }>>;
35
+ }
36
+
37
+ export type MediaViewComponent = HostComponent<NativeProps>;
38
+
39
+ export default codegenNativeComponent<NativeProps>(
40
+ 'MediaView'
41
+ ) as MediaViewComponent;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+
3
+ import { Image } from 'react-native';
4
+ import MediaViewNativeComponent from './MediaViewNativeComponent';
5
+
6
+ /** @deprecated Use ResizeMode instead */
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ /**
9
+ * MediaView - A React Native component for displaying images and videos
10
+ * Supports AVIF, PNG, JPEG, GIF, WebP images, and video files (mp4, webm, mov, etc.)
11
+ * Video URIs are automatically detected by file extension and rendered with autoplay, loop, muted, playsinline.
12
+ */
13
+ export function MediaView(props) {
14
+ const {
15
+ source,
16
+ resizeMode = 'contain',
17
+ ...restProps
18
+ } = props;
19
+ let resolvedSource;
20
+ if (typeof source === 'object' && 'uri' in source) {
21
+ resolvedSource = {
22
+ uri: source.uri
23
+ };
24
+ } else {
25
+ const resolved = Image.resolveAssetSource(source);
26
+ resolvedSource = {
27
+ uri: resolved?.uri || ''
28
+ };
29
+ }
30
+ return /*#__PURE__*/_jsx(MediaViewNativeComponent, {
31
+ ...restProps,
32
+ source: resolvedSource,
33
+ resizeMode: resizeMode
34
+ });
35
+ }
36
+ export default MediaView;
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["Image","MediaViewNativeComponent","jsx","_jsx","MediaView","props","source","resizeMode","restProps","resolvedSource","uri","resolved","resolveAssetSource"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AACA,SAAkCA,KAAK,QAAQ,cAAc;AAC7D,OAAOC,wBAAwB,MAIxB,4BAA4B;;AAInC;AAAA,SAAAC,GAAA,IAAAC,IAAA;AAYA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,SAASA,CAACC,KAAqB,EAAE;EAC/C,MAAM;IAAEC,MAAM;IAAEC,UAAU,GAAG,SAAS;IAAE,GAAGC;EAAU,CAAC,GAAGH,KAAK;EAE9D,IAAII,cAAgC;EAEpC,IAAI,OAAOH,MAAM,KAAK,QAAQ,IAAI,KAAK,IAAIA,MAAM,EAAE;IACjDG,cAAc,GAAG;MAAEC,GAAG,EAAEJ,MAAM,CAACI;IAAI,CAAC;EACtC,CAAC,MAAM;IACL,MAAMC,QAAQ,GAAGX,KAAK,CAACY,kBAAkB,CAACN,MAA4B,CAAC;IACvEG,cAAc,GAAG;MAAEC,GAAG,EAAEC,QAAQ,EAAED,GAAG,IAAI;IAAG,CAAC;EAC/C;EAEA,oBACEP,IAAA,CAACF,wBAAwB;IAAA,GACnBO,SAAS;IACbF,MAAM,EAAEG,cAAe;IACvBF,UAAU,EAAEA;EAAW,CACxB,CAAC;AAEN;AAEA,eAAeH,SAAS","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=react-native.d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["react-native.d.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,37 @@
1
+ import type { ViewProps } from 'react-native';
2
+ import type { DirectEventHandler } from 'react-native/Libraries/Types/CodegenTypes';
3
+ import type { HostComponent } from 'react-native';
4
+ /**
5
+ * Source props for media (resolved from require())
6
+ */
7
+ export interface MediaSourceProps {
8
+ /** URI of the image or video */
9
+ uri?: string;
10
+ }
11
+ /**
12
+ * Resize mode for image display (aligned with React Native Image)
13
+ */
14
+ export type ResizeMode = 'cover' | 'contain' | 'stretch' | 'center';
15
+ /**
16
+ * Native props for MediaView component
17
+ */
18
+ interface NativeProps extends ViewProps {
19
+ /** Source of the media */
20
+ source?: MediaSourceProps;
21
+ /** Resize mode for image display (aligned with React Native Image) */
22
+ resizeMode?: string;
23
+ /** Callback when loading starts */
24
+ onLoadStart?: DirectEventHandler<null>;
25
+ /** Callback when the image is loaded */
26
+ onLoad?: DirectEventHandler<null>;
27
+ /** Callback when loading ends (success or failure) */
28
+ onLoadEnd?: DirectEventHandler<null>;
29
+ /** Callback when an error occurs */
30
+ onError?: DirectEventHandler<Readonly<{
31
+ error: string;
32
+ }>>;
33
+ }
34
+ export type MediaViewComponent = HostComponent<NativeProps>;
35
+ declare const _default: MediaViewComponent;
36
+ export default _default;
37
+ //# sourceMappingURL=MediaViewNativeComponent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MediaViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/MediaViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEpE;;GAEG;AACH,UAAU,WAAY,SAAQ,SAAS;IACrC,0BAA0B;IAC1B,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,WAAW,CAAC,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACvC,wCAAwC;IACxC,MAAM,CAAC,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAClC,sDAAsD;IACtD,SAAS,CAAC,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACrC,oCAAoC;IACpC,OAAO,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CAC3D;AAED,MAAM,MAAM,kBAAkB,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;wBAIvD,kBAAkB;AAFvB,wBAEwB"}
@@ -0,0 +1,23 @@
1
+ import type { ComponentProps } from 'react';
2
+ import { type ImageRequireSource } from 'react-native';
3
+ import MediaViewNativeComponent, { type MediaViewComponent, type MediaSourceProps, type ResizeMode } from './MediaViewNativeComponent';
4
+ export type { MediaSourceProps, ResizeMode, MediaViewComponent };
5
+ /** @deprecated Use ResizeMode instead */
6
+ export type ContentMode = ResizeMode;
7
+ type NativeProps = ComponentProps<typeof MediaViewNativeComponent>;
8
+ export interface MediaViewProps extends Omit<NativeProps, 'source'> {
9
+ /** Source of the media - use require('./path/to/file') or { uri: 'https://...' } */
10
+ source: ImageRequireSource | {
11
+ uri: string;
12
+ };
13
+ /** Resize mode for display (aligned with React Native Image) */
14
+ resizeMode?: ResizeMode;
15
+ }
16
+ /**
17
+ * MediaView - A React Native component for displaying images and videos
18
+ * Supports AVIF, PNG, JPEG, GIF, WebP images, and video files (mp4, webm, mov, etc.)
19
+ * Video URIs are automatically detected by file extension and rendered with autoplay, loop, muted, playsinline.
20
+ */
21
+ export declare function MediaView(props: MediaViewProps): import("react/jsx-runtime").JSX.Element;
22
+ export default MediaView;
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,KAAK,kBAAkB,EAAS,MAAM,cAAc,CAAC;AAC9D,OAAO,wBAAwB,EAAE,EAC/B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAChB,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,kBAAkB,EAAE,CAAC;AAEjE,yCAAyC;AACzC,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC;AAErC,KAAK,WAAW,GAAG,cAAc,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEnE,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC;IACjE,oFAAoF;IACpF,MAAM,EAAE,kBAAkB,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,gEAAgE;IAChE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,2CAmB9C;AAED,eAAe,SAAS,CAAC"}
package/package.json ADDED
@@ -0,0 +1,165 @@
1
+ {
2
+ "name": "react-native-media-view",
3
+ "version": "0.2.0",
4
+ "description": "A React Native component for displaying video and images. Supports iOS only with the new architecture (Fabric).",
5
+ "main": "./lib/module/index.js",
6
+ "types": "./lib/typescript/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "source": "./src/index.tsx",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "default": "./lib/module/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "lib",
18
+ "android",
19
+ "ios",
20
+ "cpp",
21
+ "*.podspec",
22
+ "react-native.config.js",
23
+ "!ios/build",
24
+ "!android/build",
25
+ "!android/gradle",
26
+ "!android/gradlew",
27
+ "!android/gradlew.bat",
28
+ "!android/local.properties",
29
+ "!**/__tests__",
30
+ "!**/__fixtures__",
31
+ "!**/__mocks__",
32
+ "!**/.*"
33
+ ],
34
+ "scripts": {
35
+ "example": "yarn workspace react-native-media-view-example",
36
+ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
37
+ "prepare": "bob build",
38
+ "typecheck": "tsc",
39
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
40
+ "release": "release-it --only-version"
41
+ },
42
+ "keywords": [
43
+ "react-native",
44
+ "ios",
45
+ "android"
46
+ ],
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/luoxuhai/react-native-media-view.git"
50
+ },
51
+ "author": "Darkce <darkce97@gmail.com> (https://github.com/luoxuhai)",
52
+ "license": "MIT",
53
+ "bugs": {
54
+ "url": "https://github.com/luoxuhai/react-native-media-view/issues"
55
+ },
56
+ "homepage": "https://github.com/luoxuhai/react-native-media-view#readme",
57
+ "publishConfig": {
58
+ "registry": "https://registry.npmjs.org/"
59
+ },
60
+ "devDependencies": {
61
+ "@commitlint/config-conventional": "^19.8.1",
62
+ "@eslint/compat": "^1.3.2",
63
+ "@eslint/eslintrc": "^3.3.1",
64
+ "@eslint/js": "^9.35.0",
65
+ "@react-native/babel-preset": "0.83.0",
66
+ "@react-native/eslint-config": "0.83.0",
67
+ "@release-it/conventional-changelog": "^10.0.1",
68
+ "@types/react": "^19.2.0",
69
+ "commitlint": "^19.8.1",
70
+ "del-cli": "^6.0.0",
71
+ "eslint": "^9.35.0",
72
+ "eslint-config-prettier": "^10.1.8",
73
+ "eslint-plugin-prettier": "^5.5.4",
74
+ "lefthook": "^2.0.3",
75
+ "prettier": "^2.8.8",
76
+ "react": "19.2.0",
77
+ "react-native": "0.83.0",
78
+ "react-native-builder-bob": "^0.40.13",
79
+ "release-it": "^19.0.4",
80
+ "turbo": "^2.5.6",
81
+ "typescript": "^5.9.2"
82
+ },
83
+ "peerDependencies": {
84
+ "react": "*",
85
+ "react-native": "*"
86
+ },
87
+ "workspaces": [
88
+ "example"
89
+ ],
90
+ "packageManager": "yarn@4.11.0",
91
+ "react-native-builder-bob": {
92
+ "source": "src",
93
+ "output": "lib",
94
+ "targets": [
95
+ [
96
+ "module",
97
+ {
98
+ "esm": true
99
+ }
100
+ ],
101
+ [
102
+ "typescript",
103
+ {
104
+ "project": "tsconfig.build.json"
105
+ }
106
+ ]
107
+ ]
108
+ },
109
+ "codegenConfig": {
110
+ "name": "MediaViewSpec",
111
+ "type": "all",
112
+ "jsSrcsDir": "src",
113
+ "android": {
114
+ "javaPackageName": "com.mediaview"
115
+ },
116
+ "ios": {
117
+ "components": {
118
+ "MediaView": {
119
+ "className": "MediaView"
120
+ }
121
+ }
122
+ }
123
+ },
124
+ "prettier": {
125
+ "quoteProps": "consistent",
126
+ "singleQuote": true,
127
+ "tabWidth": 2,
128
+ "trailingComma": "es5",
129
+ "useTabs": false
130
+ },
131
+ "commitlint": {
132
+ "extends": [
133
+ "@commitlint/config-conventional"
134
+ ]
135
+ },
136
+ "release-it": {
137
+ "git": {
138
+ "commitMessage": "chore: release ${version}",
139
+ "tagName": "v${version}"
140
+ },
141
+ "npm": {
142
+ "publish": true
143
+ },
144
+ "github": {
145
+ "release": true
146
+ },
147
+ "plugins": {
148
+ "@release-it/conventional-changelog": {
149
+ "preset": {
150
+ "name": "angular"
151
+ }
152
+ }
153
+ }
154
+ },
155
+ "create-react-native-library": {
156
+ "type": "fabric-view",
157
+ "languages": "kotlin-objc",
158
+ "tools": [
159
+ "eslint",
160
+ "lefthook",
161
+ "release-it"
162
+ ],
163
+ "version": "0.57.0"
164
+ }
165
+ }
@@ -0,0 +1,20 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "react-native-media-view"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/luoxuhai/react-native-media-view.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17
+ s.private_header_files = "ios/**/*.h"
18
+
19
+ install_modules_dependencies(s)
20
+ end
@@ -0,0 +1,41 @@
1
+ import { codegenNativeComponent } from 'react-native';
2
+ import type { ViewProps } from 'react-native';
3
+ import type { DirectEventHandler } from 'react-native/Libraries/Types/CodegenTypes';
4
+ import type { HostComponent } from 'react-native';
5
+
6
+ /**
7
+ * Source props for media (resolved from require())
8
+ */
9
+ export interface MediaSourceProps {
10
+ /** URI of the image or video */
11
+ uri?: string;
12
+ }
13
+
14
+ /**
15
+ * Resize mode for image display (aligned with React Native Image)
16
+ */
17
+ export type ResizeMode = 'cover' | 'contain' | 'stretch' | 'center';
18
+
19
+ /**
20
+ * Native props for MediaView component
21
+ */
22
+ interface NativeProps extends ViewProps {
23
+ /** Source of the media */
24
+ source?: MediaSourceProps;
25
+ /** Resize mode for image display (aligned with React Native Image) */
26
+ resizeMode?: string;
27
+ /** Callback when loading starts */
28
+ onLoadStart?: DirectEventHandler<null>;
29
+ /** Callback when the image is loaded */
30
+ onLoad?: DirectEventHandler<null>;
31
+ /** Callback when loading ends (success or failure) */
32
+ onLoadEnd?: DirectEventHandler<null>;
33
+ /** Callback when an error occurs */
34
+ onError?: DirectEventHandler<Readonly<{ error: string }>>;
35
+ }
36
+
37
+ export type MediaViewComponent = HostComponent<NativeProps>;
38
+
39
+ export default codegenNativeComponent<NativeProps>(
40
+ 'MediaView'
41
+ ) as MediaViewComponent;
package/src/index.tsx ADDED
@@ -0,0 +1,49 @@
1
+ import type { ComponentProps } from 'react';
2
+ import { type ImageRequireSource, Image } from 'react-native';
3
+ import MediaViewNativeComponent, {
4
+ type MediaViewComponent,
5
+ type MediaSourceProps,
6
+ type ResizeMode,
7
+ } from './MediaViewNativeComponent';
8
+
9
+ export type { MediaSourceProps, ResizeMode, MediaViewComponent };
10
+
11
+ /** @deprecated Use ResizeMode instead */
12
+ export type ContentMode = ResizeMode;
13
+
14
+ type NativeProps = ComponentProps<typeof MediaViewNativeComponent>;
15
+
16
+ export interface MediaViewProps extends Omit<NativeProps, 'source'> {
17
+ /** Source of the media - use require('./path/to/file') or { uri: 'https://...' } */
18
+ source: ImageRequireSource | { uri: string };
19
+ /** Resize mode for display (aligned with React Native Image) */
20
+ resizeMode?: ResizeMode;
21
+ }
22
+
23
+ /**
24
+ * MediaView - A React Native component for displaying images and videos
25
+ * Supports AVIF, PNG, JPEG, GIF, WebP images, and video files (mp4, webm, mov, etc.)
26
+ * Video URIs are automatically detected by file extension and rendered with autoplay, loop, muted, playsinline.
27
+ */
28
+ export function MediaView(props: MediaViewProps) {
29
+ const { source, resizeMode = 'contain', ...restProps } = props;
30
+
31
+ let resolvedSource: MediaSourceProps;
32
+
33
+ if (typeof source === 'object' && 'uri' in source) {
34
+ resolvedSource = { uri: source.uri };
35
+ } else {
36
+ const resolved = Image.resolveAssetSource(source as ImageRequireSource);
37
+ resolvedSource = { uri: resolved?.uri || '' };
38
+ }
39
+
40
+ return (
41
+ <MediaViewNativeComponent
42
+ {...restProps}
43
+ source={resolvedSource}
44
+ resizeMode={resizeMode}
45
+ />
46
+ );
47
+ }
48
+
49
+ export default MediaView;
@@ -0,0 +1,31 @@
1
+ // Type declarations for React Native internal modules
2
+ // These are required for React Native Fabric component codegen
3
+
4
+ declare module 'react-native/Libraries/Types/CodegenTypes' {
5
+ export type Double = number;
6
+ export type Float = number;
7
+ export type Int32 = number;
8
+ export type UnsafeObject = object;
9
+ export type WithDefault<T, _V> = T | _V | undefined;
10
+ export type DirectEventHandler<T> = (event: { nativeEvent: T }) => void;
11
+ export type BubblingEventHandler<T> = (event: { nativeEvent: T }) => void;
12
+ }
13
+
14
+ declare module 'react-native/Libraries/Utilities/codegenNativeComponent' {
15
+ import type { HostComponent } from 'react-native';
16
+
17
+ export default function codegenNativeComponent<P>(
18
+ componentName: string,
19
+ options?: {
20
+ interfaceOnly?: boolean;
21
+ paperComponentName?: string;
22
+ excludedPlatforms?: string[];
23
+ }
24
+ ): HostComponent<P>;
25
+ }
26
+
27
+ declare module 'react-native/Libraries/Utilities/codegenNativeCommands' {
28
+ export default function codegenNativeCommands<T>(options: {
29
+ supportedCommands: ReadonlyArray<keyof T>;
30
+ }): T;
31
+ }