react-native-media3-player 0.0.1 → 1.0.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.
@@ -1,37 +1,55 @@
1
+ // Apply the Android library plugin
1
2
  apply plugin: "com.android.library"
3
+ // Apply the Kotlin Android plugin for Kotlin language support
2
4
  apply plugin: "org.jetbrains.kotlin.android"
5
+ // Apply the React Native plugin to enable React Native integration
6
+ apply plugin: "com.facebook.react"
3
7
 
4
8
  android {
9
+ // Set the namespace for the generated R class and manifest
5
10
  namespace "com.rnmedia3playerdemo"
11
+
12
+ // Compile SDK version to build against
6
13
  compileSdkVersion 35
7
14
 
8
15
  defaultConfig {
16
+ // Minimum SDK version supported by the library
9
17
  minSdkVersion 24
18
+ // Target SDK version for compatibility and behaviors
10
19
  targetSdkVersion 34
11
20
  }
12
21
 
13
22
  compileOptions {
23
+ // Specify Java source and target compatibility
14
24
  sourceCompatibility JavaVersion.VERSION_17
15
25
  targetCompatibility JavaVersion.VERSION_17
16
26
  }
17
27
 
18
28
  kotlinOptions {
29
+ // Set the Kotlin JVM target version
19
30
  jvmTarget = "17"
20
31
  }
32
+
33
+ sourceSets {
34
+ main {
35
+ // Include codegen outputs (for TurboModules, Fabric, etc.) in the main sources
36
+ java.srcDirs += "${buildDir}/generated/source/codegen/java"
37
+ }
38
+ }
21
39
  }
22
40
 
23
41
  dependencies {
24
- // Required for React Native bridge
25
- implementation "com.facebook.react:react-native:+"
42
+ // Dependency for React Native bridge (provided by the app, NOT bundled in aar)
43
+ compileOnly "com.facebook.react:react-android"
26
44
 
27
- // Kotlin standard lib
28
- implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.0"
45
+ // Kotlin standard library dependency
46
+ implementation "org.jetbrains.kotlin:kotlin-stdlib"
29
47
 
30
- // Media3 dependencies
48
+ // AndroidX Media3 ExoPlayer, UI components, and Common classes
31
49
  implementation "androidx.media3:media3-exoplayer:1.4.1"
32
50
  implementation "androidx.media3:media3-ui:1.4.1"
33
51
  implementation "androidx.media3:media3-common:1.4.1"
34
52
 
35
- // Useful Android extensions
53
+ // Android core utilities and extensions (KTX)
36
54
  implementation "androidx.core:core-ktx:1.13.1"
37
55
  }
@@ -2,13 +2,25 @@ package com.rnmedia3playerdemo
2
2
 
3
3
  import com.facebook.react.ReactPackage
4
4
  import com.facebook.react.bridge.NativeModule
5
- import com.facebook.react.uimanager.ViewManager
6
5
  import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
7
 
8
+ /**
9
+ * Media3Package integrates the custom Media3PlayerViewManager with React Native.
10
+ *
11
+ * This package does not register any NativeModules. It only provides the
12
+ * Media3PlayerViewManager for use as a native UI component from JavaScript.
13
+ */
8
14
  class Media3Package : ReactPackage {
9
- override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> = emptyList()
15
+ /**
16
+ * No native modules are exported by this package.
17
+ */
18
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
19
+ emptyList()
10
20
 
11
- override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
12
- return listOf(Media3PlayerViewManager(reactContext))
13
- }
21
+ /**
22
+ * Registers Media3PlayerViewManager as the only custom ViewManager for this package.
23
+ */
24
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
25
+ listOf(Media3PlayerViewManager())
14
26
  }
@@ -9,26 +9,32 @@ import androidx.media3.common.Player
9
9
  import androidx.media3.common.PlaybackException
10
10
  import androidx.media3.exoplayer.ExoPlayer
11
11
  import androidx.media3.ui.PlayerView
12
- import com.facebook.react.bridge.ReactContext
13
- import com.facebook.react.uimanager.events.RCTEventEmitter
14
12
  import com.facebook.react.bridge.Arguments
13
+ import com.facebook.react.bridge.ReactContext
14
+ import com.facebook.react.bridge.WritableMap
15
+ import com.facebook.react.uimanager.UIManagerHelper
16
+ import com.facebook.react.uimanager.events.Event
15
17
 
16
18
  /**
17
- * Media3PlayerView
18
- *
19
- * Handles ExoPlayer playback and exposes events to React Native:
20
- * - onReady
21
- * - onEnd
22
- * - onError
19
+ * Custom view that wraps ExoPlayer and PlayerView, integrating with React Native.
23
20
  */
24
21
  class Media3PlayerView(context: Context) : FrameLayout(context) {
22
+ // ExoPlayer instance to handle media playback
25
23
  private var exoPlayer: ExoPlayer? = null
24
+ // PlayerView to display video content
26
25
  private var playerView: PlayerView
26
+ // Currently loaded media source URI
27
27
  private var sourceUri: String? = null
28
+ // Should video autoplay when ready
28
29
  private var autoplay: Boolean = false
30
+ // Play state controlled by the JS prop
29
31
  private var play: Boolean = false
32
+ // Mute state controlled by the JS prop
30
33
  private var mute: Boolean = false
31
34
 
35
+ /**
36
+ * Constructor: initialize the PlayerView and attach it to the layout.
37
+ */
32
38
  init {
33
39
  playerView = PlayerView(context)
34
40
  playerView.layoutParams = LayoutParams(
@@ -38,16 +44,23 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
38
44
  addView(playerView)
39
45
  }
40
46
 
47
+ /**
48
+ * Initializes ExoPlayer and attaches a listener for player events.
49
+ * Ensures only one instance exists.
50
+ */
41
51
  private fun initializePlayer() {
42
52
  if (exoPlayer == null) {
43
53
  exoPlayer = ExoPlayer.Builder(context).build()
44
54
  playerView.player = exoPlayer
45
55
 
56
+ // Listen for playback state changes and errors
46
57
  exoPlayer?.addListener(object : Player.Listener {
47
58
  override fun onPlaybackStateChanged(state: Int) {
48
59
  when (state) {
49
60
  Player.STATE_READY -> {
50
- sendEvent("onReady")
61
+ // Notify React Native that player is ready
62
+ sendEvent("topReady")
63
+ // If autoplay is enabled, start playback when ready
51
64
  if (autoplay) {
52
65
  exoPlayer?.playWhenReady = true
53
66
  exoPlayer?.play()
@@ -55,62 +68,105 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
55
68
  }
56
69
  }
57
70
  Player.STATE_ENDED -> {
58
- sendEvent("onEnd")
71
+ // Notify React Native that playback has ended
72
+ sendEvent("topEnd")
59
73
  }
60
74
  }
61
75
  }
62
76
 
63
77
  override fun onPlayerError(error: PlaybackException) {
64
- sendEvent("onError", error.message ?: "Unknown error")
78
+ // Report playback errors to React Native
79
+ sendEvent("topError", error.message ?: "Unknown error")
65
80
  }
66
81
  })
67
82
  }
68
83
  }
69
84
 
85
+ /**
86
+ * Sets the video source from a URI string and prepares the player.
87
+ * @param uriString Video source URI as a string
88
+ */
70
89
  fun setSource(uriString: String?) {
71
90
  if (uriString.isNullOrEmpty()) return
72
91
  sourceUri = uriString
73
92
 
93
+ // Ensure player is initialized
74
94
  initializePlayer()
75
95
 
96
+ // Set and prepare the media item for playback
76
97
  val item = MediaItem.fromUri(Uri.parse(uriString))
77
98
  exoPlayer?.setMediaItem(item)
78
99
  exoPlayer?.prepare()
79
100
  }
80
101
 
102
+ /**
103
+ * Sets the autoplay flag to control whether playback starts when player is ready.
104
+ * @param value Boolean value from JS prop
105
+ */
81
106
  fun setAutoplay(value: Boolean) {
82
107
  autoplay = value
83
108
  }
84
109
 
110
+ /**
111
+ * Set the play state of the player.
112
+ * If true, the player will start/resume playback; otherwise, pause.
113
+ * @param value Boolean value from JS prop
114
+ */
85
115
  fun setPlay(value: Boolean) {
86
116
  play = value
87
- // if you want to disable autoplay with play (prop) -
88
- // if (exoPlayer?.playbackState == Player.STATE_READY)
89
- if (play) {
90
- exoPlayer?.play()
91
- } else {
92
- exoPlayer?.pause()
93
- }
94
- // }
117
+ // Optionally: Disabling autoplay if play is used directly
118
+ // Only play or pause if player is ready
119
+ if (play) {
120
+ exoPlayer?.play()
121
+ } else {
122
+ exoPlayer?.pause()
123
+ }
95
124
  }
96
125
 
126
+ /**
127
+ * Sets the mute state of the player.
128
+ * @param value Boolean indicating if the audio should be muted
129
+ */
97
130
  fun setMute(value: Boolean) {
98
131
  mute = value
99
132
  exoPlayer?.volume = if (mute) 0f else 1f
100
133
  }
101
134
 
135
+ /**
136
+ * Releases the player and cleans up resources.
137
+ * This should be called when the view is destroyed.
138
+ */
102
139
  fun releasePlayer() {
103
140
  exoPlayer?.release()
104
141
  exoPlayer = null
105
142
  }
106
143
 
107
- // 👇 helper function to send events to React Native
144
+ /**
145
+ * Helper function to send events and optional data payloads to React Native JS side.
146
+ * @param eventName Name of the event (e.g., "topReady", "topError")
147
+ * @param message Optional message string to pass in the event payload
148
+ */
108
149
  private fun sendEvent(eventName: String, message: String? = null) {
109
150
  val reactContext = context as? ReactContext ?: return
110
- val event = Arguments.createMap()
111
- message?.let { event.putString("message", it) }
112
- reactContext
113
- .getJSModule(RCTEventEmitter::class.java)
114
- .receiveEvent(id, eventName, event)
151
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
152
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
153
+ val payload = Arguments.createMap()
154
+ message?.let { payload.putString("message", it) }
155
+ eventDispatcher?.dispatchEvent(
156
+ Media3Event(surfaceId, id, eventName, payload)
157
+ )
158
+ }
159
+
160
+ /**
161
+ * Custom event class to bridge native events to React Native UIManager system.
162
+ */
163
+ private class Media3Event(
164
+ surfaceId: Int,
165
+ viewId: Int,
166
+ private val name: String,
167
+ private val payload: WritableMap
168
+ ) : Event<Media3Event>(surfaceId, viewId) {
169
+ override fun getEventName(): String = name
170
+ override fun getEventData(): WritableMap = payload
115
171
  }
116
172
  }
@@ -1,61 +1,106 @@
1
1
  package com.rnmedia3playerdemo
2
2
 
3
- import com.facebook.react.bridge.ReactApplicationContext
4
3
  import com.facebook.react.bridge.ReadableMap
4
+ import com.facebook.react.module.annotations.ReactModule
5
5
  import com.facebook.react.uimanager.SimpleViewManager
6
6
  import com.facebook.react.uimanager.ThemedReactContext
7
+ import com.facebook.react.uimanager.ViewManagerDelegate
7
8
  import com.facebook.react.uimanager.annotations.ReactProp
9
+ import com.facebook.react.viewmanagers.Media3PlayerViewManagerDelegate
10
+ import com.facebook.react.viewmanagers.Media3PlayerViewManagerInterface
8
11
 
9
12
  /**
10
- * Media3PlayerViewManager
11
- *
12
- * Exposes the native view to React Native.
13
- * Props: source { uri: string }, autoplay, play, mute
14
- * Events: onReady, onEnd, onError
13
+ * ViewManager for the custom Media3PlayerView.
14
+ * Responsible for creating the view, handling props from JS, and managing events.
15
15
  */
16
- class Media3PlayerViewManager(private val reactContext: ReactApplicationContext) :
17
- SimpleViewManager<Media3PlayerView>() {
16
+ @ReactModule(name = Media3PlayerViewManager.NAME)
17
+ class Media3PlayerViewManager :
18
+ SimpleViewManager<Media3PlayerView>(),
19
+ Media3PlayerViewManagerInterface<Media3PlayerView> {
18
20
 
19
- override fun getName(): String = "Media3PlayerView"
21
+ companion object {
22
+ // The name used to reference this view manager from JS
23
+ const val NAME = "Media3PlayerView"
24
+ }
25
+
26
+ // Delegate handles communication and prop mapping automatically when generated by Codegen
27
+ private val delegate = Media3PlayerViewManagerDelegate(this)
28
+
29
+ /**
30
+ * Returns the delegate instance for prop/event mapping.
31
+ */
32
+ override fun getDelegate(): ViewManagerDelegate<Media3PlayerView> = delegate
33
+
34
+ /**
35
+ * Returns the unique name for this ViewManager, used by React Native.
36
+ */
37
+ override fun getName(): String = NAME
20
38
 
39
+ /**
40
+ * Creates and returns a new Media3PlayerView instance for this ViewManager.
41
+ * @param reactContext ThemedReactContext provided by React Native
42
+ */
21
43
  override fun createViewInstance(reactContext: ThemedReactContext): Media3PlayerView {
22
44
  return Media3PlayerView(reactContext)
23
45
  }
24
46
 
47
+ /**
48
+ * Sets the "source" property on the Media3PlayerView.
49
+ * Reads the "uri" string from the passed ReadableMap and loads it into the native view.
50
+ */
25
51
  @ReactProp(name = "source")
26
- fun setSource(view: Media3PlayerView, source: ReadableMap?) {
27
- val uri: String? = source?.getString("uri")
52
+ override fun setSource(view: Media3PlayerView, source: ReadableMap?) {
53
+ val uri = source?.getString("uri")
28
54
  if (!uri.isNullOrEmpty()) {
29
55
  view.setSource(uri)
30
56
  }
31
57
  }
32
58
 
59
+ /**
60
+ * Sets the "autoplay" property on the Media3PlayerView.
61
+ * Controls whether playback should start automatically when ready.
62
+ */
33
63
  @ReactProp(name = "autoplay", defaultBoolean = false)
34
- fun setAutoplay(view: Media3PlayerView, autoplay: Boolean) {
35
- view.setAutoplay(autoplay)
64
+ override fun setAutoplay(view: Media3PlayerView, value: Boolean) {
65
+ view.setAutoplay(value)
36
66
  }
37
67
 
68
+ /**
69
+ * Sets the "play" property on the Media3PlayerView.
70
+ * Controls whether the player should be playing or paused.
71
+ */
38
72
  @ReactProp(name = "play", defaultBoolean = false)
39
- fun setPlay(view: Media3PlayerView, play: Boolean) {
40
- view.setPlay(play)
73
+ override fun setPlay(view: Media3PlayerView, value: Boolean) {
74
+ view.setPlay(value)
41
75
  }
42
76
 
77
+ /**
78
+ * Sets the "mute" property on the Media3PlayerView.
79
+ * Controls whether the audio should be muted.
80
+ */
43
81
  @ReactProp(name = "mute", defaultBoolean = false)
44
- fun setMute(view: Media3PlayerView, mute: Boolean) {
45
- view.setMute(mute)
82
+ override fun setMute(view: Media3PlayerView, value: Boolean) {
83
+ view.setMute(value)
46
84
  }
47
85
 
86
+ /**
87
+ * Called when the React Native view instance is about to be dropped/remounted.
88
+ * Ensures resources are released to avoid leaks.
89
+ */
48
90
  override fun onDropViewInstance(view: Media3PlayerView) {
49
91
  super.onDropViewInstance(view)
50
92
  view.releasePlayer()
51
93
  }
52
94
 
53
- // 👇 Expose custom events to JS
95
+ /**
96
+ * Exports native direct event types to React Native for event binding in JS.
97
+ * Maps native event names to the registration name expected by React.
98
+ */
54
99
  override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
55
100
  return mutableMapOf(
56
- "onReady" to mapOf("registrationName" to "onReady"),
57
- "onEnd" to mapOf("registrationName" to "onEnd"),
58
- "onError" to mapOf("registrationName" to "onError")
101
+ "topReady" to mapOf("registrationName" to "onReady"),
102
+ "topEnd" to mapOf("registrationName" to "onEnd"),
103
+ "topError" to mapOf("registrationName" to "onError")
59
104
  )
60
105
  }
61
106
  }
package/index.d.ts CHANGED
@@ -1,15 +1,16 @@
1
- import { ViewStyle } from 'react-native';
2
1
  import * as React from 'react';
2
+ import {ViewStyle} from 'react-native';
3
3
 
4
4
  export interface Media3PlayerProps {
5
5
  style?: ViewStyle | ViewStyle[];
6
- source: { uri: string };
6
+ source: {uri: string};
7
7
  autoplay?: boolean;
8
8
  play?: boolean;
9
9
  mute?: boolean;
10
10
  onReady?: () => void;
11
11
  onEnd?: () => void;
12
- onError?: (error: any) => void;
12
+ onError?: (event: {nativeEvent: {message: string}}) => void;
13
13
  }
14
14
 
15
- export default class Media3Player extends React.Component<Media3PlayerProps> {}
15
+ declare const Media3Player: React.FC<Media3PlayerProps>;
16
+ export default Media3Player;
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "react-native-media3-player",
3
- "version": "0.0.1",
3
+ "version": "1.0.0",
4
4
  "description": "React Native Media3 Player component for Android (ExoPlayer 3 integration).",
5
5
  "main": "index.js",
6
+ "react-native": "index.js",
6
7
  "types": "index.d.ts",
7
8
  "keywords": [
8
9
  "react-native",
@@ -31,5 +32,13 @@
31
32
  "src",
32
33
  "index.js",
33
34
  "index.d.ts"
34
- ]
35
+ ],
36
+ "codegenConfig": {
37
+ "name": "RNMedia3PlayerSpec",
38
+ "type": "components",
39
+ "jsSrcsDir": "src",
40
+ "android": {
41
+ "javaPackageName": "com.rnmedia3playerdemo"
42
+ }
43
+ }
35
44
  }
@@ -1,20 +1,20 @@
1
1
  import React from 'react';
2
- import {requireNativeComponent, StyleSheet} from 'react-native';
3
-
4
- // Link to native view
5
- const NativeMedia3Player = requireNativeComponent('Media3PlayerView');
2
+ import {StyleSheet} from 'react-native';
3
+ // Import the native codegen-wrapped player view component
4
+ import NativeMedia3PlayerView from './Media3PlayerNativeComponent';
6
5
 
7
6
  /**
8
- * Media3Player
7
+ * Media3Player React component
9
8
  *
10
9
  * Props:
11
- * - source: { uri: string } ✅ required
12
- * - autoplay: boolean
13
- * - play: boolean
14
- * - mute: boolean
15
- * - onReady: function
16
- * - onEnd: function
17
- * - onError: function
10
+ * - style: (optional) additional style overrides
11
+ * - source: { uri: string } – required, the media URI to load
12
+ * - autoplay: (optional, default false) – whether playback should start automatically when ready
13
+ * - play: (optional, default false) – whether playback should currently be running (true = play, false = pause)
14
+ * - mute: (optional, default false) – whether audio should be muted
15
+ * - onReady: callback when player is ready
16
+ * - onEnd: callback when playback has ended
17
+ * - onError: callback when an error occurs during playback or loading
18
18
  */
19
19
  export default function Media3Player({
20
20
  style,
@@ -26,14 +26,16 @@ export default function Media3Player({
26
26
  onEnd,
27
27
  onError,
28
28
  }) {
29
+ // Validate that source.uri is provided; warn and render nothing if missing
29
30
  if (!source || !source.uri) {
30
- console.warn('Media3Player: "source" prop with a valid "uri" is required.');
31
+ console.warn('Media3Player: "source.uri" is required.');
31
32
  return null;
32
33
  }
33
34
 
35
+ // Render the native player view, passing all relevant props and composing the style
34
36
  return (
35
- <NativeMedia3Player
36
- style={[styles.default, style]} // ensure style is never undefined
37
+ <NativeMedia3PlayerView
38
+ style={[styles.default, style]}
37
39
  source={source}
38
40
  autoplay={autoplay}
39
41
  play={play}
@@ -45,10 +47,11 @@ export default function Media3Player({
45
47
  );
46
48
  }
47
49
 
50
+ // Default styling applied to the player view unless overridden via 'style' prop
48
51
  const styles = StyleSheet.create({
49
52
  default: {
50
- width: '100%',
51
- height: 250,
52
- backgroundColor: 'black',
53
+ width: '100%', // Occupy full width of parent
54
+ height: 250, // Fixed height for the player
55
+ backgroundColor: 'black', // Default background color
53
56
  },
54
57
  });
@@ -0,0 +1,37 @@
1
+ // Import the base ViewProps type from React Native to extend the native component's props
2
+ import type {ViewProps} from 'react-native';
3
+ // Import the DirectEventHandler type used for native-to-JS event callback typings
4
+ import type {DirectEventHandler} from 'react-native/Libraries/Types/CodegenTypes';
5
+ // Import codegenNativeComponent to register the native UI component for use in React Native
6
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
7
+
8
+ // Event type emitted when the player is ready
9
+ type OnReadyEvent = Readonly<{}>;
10
+ // Event type emitted when playback reaches the end
11
+ type OnEndEvent = Readonly<{}>;
12
+ // Event type emitted when an error occurs during playback
13
+ type OnErrorEvent = Readonly<{
14
+ message: string; // Error message describing what went wrong
15
+ }>;
16
+
17
+ // Props supported by the native Media3PlayerView component
18
+ export interface NativeProps extends ViewProps {
19
+ // Source URI for the media to play
20
+ source?: Readonly<{uri: string}>;
21
+ // Whether playback should start automatically when ready
22
+ autoplay?: boolean;
23
+ // Whether playback should currently be running (true = play, false = pause)
24
+ play?: boolean;
25
+ // Whether audio should be muted
26
+ mute?: boolean;
27
+
28
+ // Callback invoked when the player is ready to play
29
+ onReady?: DirectEventHandler<OnReadyEvent>;
30
+ // Callback invoked when playback reaches the end of the media
31
+ onEnd?: DirectEventHandler<OnEndEvent>;
32
+ // Callback invoked when an error occurs in playback or loading
33
+ onError?: DirectEventHandler<OnErrorEvent>;
34
+ }
35
+
36
+ // Create and export the code-generated native component using the props interface
37
+ export default codegenNativeComponent<NativeProps>('Media3PlayerView');