@bluebillywig/react-native-bb-player 8.42.7 → 8.42.8

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.
@@ -4,6 +4,8 @@ import android.app.Activity
4
4
  import android.os.Handler
5
5
  import android.os.Looper
6
6
  import android.util.Log
7
+ import android.view.MotionEvent
8
+ import android.view.View
7
9
  import android.widget.FrameLayout
8
10
  import androidx.collection.ArrayMap
9
11
  import androidx.mediarouter.app.MediaRouteButton
@@ -31,8 +33,40 @@ private inline fun debugLog(tag: String, message: () -> String) {
31
33
  /**
32
34
  * React Native View for Blue Billywig Native Player
33
35
  *
34
- * Uses FrameLayout as base for proper native Android layout handling.
35
- * Events are sent via React Native's RCTEventEmitter.
36
+ * This view wraps the native BBNativePlayerView and handles the integration with
37
+ * React Native's view system. The key challenge is that React Native uses Yoga for
38
+ * layout, which can interfere with native Android views that have their own layout
39
+ * and touch handling requirements (like ExoPlayer's StyledPlayerView with controlbar).
40
+ *
41
+ * ## Native Layout Integration
42
+ *
43
+ * To ensure the native player controls work correctly, this view:
44
+ *
45
+ * 1. **Overrides requestLayout()** - Ensures layout requests propagate correctly through
46
+ * React Native's view hierarchy by posting to the choreographer.
47
+ *
48
+ * 2. **Overrides onMeasure()** - Uses native Android measurement (MeasureSpec.EXACTLY)
49
+ * instead of letting Yoga determine the size, ensuring the player and its controls
50
+ * receive proper dimensions.
51
+ *
52
+ * 3. **Overrides onLayout()** - Explicitly layouts child views to fill the container,
53
+ * which is necessary because React Native's Yoga layout doesn't automatically
54
+ * propagate layout to native child views.
55
+ *
56
+ * 4. **Overrides onInterceptTouchEvent()** - Returns false to ensure touch events
57
+ * always reach the child BBNativePlayerView, allowing the player's controlbar
58
+ * to respond to taps.
59
+ *
60
+ * ## Why This Is Necessary
61
+ *
62
+ * React Native's Yoga layout system is designed for flexbox-based UI, not for native
63
+ * views with complex internal view hierarchies. The BBNativePlayerView contains an
64
+ * ExoPlayer StyledPlayerView which has its own gesture detectors and controlbar that
65
+ * need to receive touch events directly. Without these overrides, React Native's
66
+ * touch handling system intercepts events before they reach the native player controls.
67
+ *
68
+ * This approach is similar to how Expo's ExpoView handles native views with
69
+ * `shouldUseAndroidLayout = true`.
36
70
  */
37
71
  class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(reactContext),
38
72
  BBNativePlayerViewDelegate {
@@ -56,18 +90,108 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
56
90
  setBackgroundColor(android.graphics.Color.BLACK)
57
91
  }
58
92
 
93
+ // ==================================================================================
94
+ // NATIVE LAYOUT INTEGRATION
95
+ // These overrides ensure the native player view and its controls work correctly
96
+ // within React Native's Yoga-based layout system.
97
+ // ==================================================================================
98
+
59
99
  /**
60
- * Override onLayout to ensure child views receive proper bounds.
61
- * This is critical for React Native Fabric where layout isn't automatically propagated.
100
+ * Override requestLayout to ensure layout requests are properly handled.
101
+ *
102
+ * React Native's layout system uses Yoga which processes layout asynchronously.
103
+ * Native Android views expect requestLayout() to trigger a synchronous layout pass.
104
+ * This override posts a layout request to ensure proper propagation through the
105
+ * view hierarchy while being frame-aligned via Choreographer for performance.
106
+ *
107
+ * Without this, the native player view may not update its layout when needed,
108
+ * causing issues with control positioning and visibility.
109
+ */
110
+ override fun requestLayout() {
111
+ super.requestLayout()
112
+
113
+ // Post to ensure layout happens on the next frame, avoiding layout-during-layout issues
114
+ // This is a common pattern for native views embedded in React Native
115
+ post {
116
+ measure(
117
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
118
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
119
+ )
120
+ layout(left, top, right, bottom)
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Override onMeasure to use native Android measurement.
126
+ *
127
+ * By default, React Native's Yoga layout may pass UNSPECIFIED or AT_MOST specs,
128
+ * which can confuse native views expecting EXACTLY specs. This ensures the player
129
+ * view receives precise dimensions matching the container size set by React Native.
130
+ *
131
+ * Performance note: This is called during the measure pass and is optimized to
132
+ * avoid unnecessary work by only measuring children when we have valid dimensions.
133
+ */
134
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
135
+ // Get the exact dimensions from React Native's layout
136
+ val width = MeasureSpec.getSize(widthMeasureSpec)
137
+ val height = MeasureSpec.getSize(heightMeasureSpec)
138
+
139
+ // Set our measured dimensions
140
+ setMeasuredDimension(width, height)
141
+
142
+ // Measure all children with EXACTLY specs to ensure they fill the container
143
+ // This is critical for the player view to receive proper dimensions
144
+ if (width > 0 && height > 0) {
145
+ val childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)
146
+ val childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
147
+
148
+ for (i in 0 until childCount) {
149
+ getChildAt(i)?.measure(childWidthSpec, childHeightSpec)
150
+ }
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Override onLayout to explicitly position child views.
156
+ *
157
+ * React Native's Yoga layout calculates positions but doesn't automatically apply
158
+ * them to native child views. This explicitly layouts all children to fill the
159
+ * container, which is necessary for the BBNativePlayerView to render correctly.
62
160
  */
63
161
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
64
162
  super.onLayout(changed, left, top, right, bottom)
65
- // Explicitly layout all children to fill the container
163
+
164
+ val width = right - left
165
+ val height = bottom - top
166
+
167
+ // Layout all children to fill the entire container
66
168
  for (i in 0 until childCount) {
67
- getChildAt(i)?.layout(0, 0, right - left, bottom - top)
169
+ getChildAt(i)?.layout(0, 0, width, height)
68
170
  }
69
171
  }
70
172
 
173
+ /**
174
+ * Never intercept touch events - let them pass through to child views.
175
+ *
176
+ * This is CRITICAL for the player controlbar to work. React Native's gesture
177
+ * handling system can intercept touch events before they reach native views.
178
+ * By always returning false, we ensure:
179
+ *
180
+ * 1. Single taps reach the PlayerView to toggle the controlbar
181
+ * 2. Double taps reach the PlayerView for seek functionality
182
+ * 3. Swipes and other gestures work for any interactive elements
183
+ *
184
+ * The BBNativePlayerView handles its own touch events internally through
185
+ * ExoPlayer's StyledPlayerView and custom gesture detectors.
186
+ */
187
+ override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
188
+ return false
189
+ }
190
+
191
+ // ==================================================================================
192
+ // END NATIVE LAYOUT INTEGRATION
193
+ // ==================================================================================
194
+
71
195
  // Timer for periodic time updates (opt-in for performance)
72
196
  private val timeUpdateHandler = Handler(Looper.getMainLooper())
73
197
  private var timeUpdateRunnable: Runnable? = null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bluebillywig/react-native-bb-player",
3
- "version": "8.42.7",
3
+ "version": "8.42.8",
4
4
  "description": "Blue Billywig Native Video Player for React Native - iOS AVPlayer and Android ExoPlayer integration",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",