@stream-io/video-react-native-sdk 1.9.20 → 1.9.21
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/CHANGELOG.md +7 -0
- package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt +11 -116
- package/android/src/main/java/com/streamvideo/reactnative/util/PiPHelper.kt +138 -0
- package/dist/commonjs/version.js +1 -1
- package/dist/module/version.js +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.9.21](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.9.20...@stream-io/video-react-native-sdk-1.9.21) (2025-02-06)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* NoClassDefFoundError on PiP on android 7 and below ([70ac465](https://github.com/GetStream/stream-video-js/commit/70ac4656b5f0b42c649f38ff288adb47eff02907))
|
|
11
|
+
|
|
5
12
|
## [1.9.20](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.9.19...@stream-io/video-react-native-sdk-1.9.20) (2025-02-05)
|
|
6
13
|
|
|
7
14
|
### Dependency Updates
|
|
@@ -1,27 +1,18 @@
|
|
|
1
1
|
package com.streamvideo.reactnative
|
|
2
2
|
|
|
3
|
-
import android.app.Activity
|
|
4
|
-
import android.app.AppOpsManager
|
|
5
|
-
import android.app.PictureInPictureParams
|
|
6
3
|
import android.content.BroadcastReceiver
|
|
7
4
|
import android.content.Context
|
|
8
5
|
import android.content.Intent
|
|
9
6
|
import android.content.IntentFilter
|
|
10
|
-
import android.content.pm.ActivityInfo
|
|
11
|
-
import android.content.pm.PackageManager
|
|
12
7
|
import android.net.Uri
|
|
13
8
|
import android.os.Build
|
|
14
9
|
import android.os.PowerManager
|
|
15
|
-
import android.os.Process
|
|
16
|
-
import android.util.Log
|
|
17
|
-
import android.util.Rational
|
|
18
|
-
import androidx.annotation.RequiresApi
|
|
19
|
-
import com.facebook.react.ReactActivity
|
|
20
10
|
import com.facebook.react.bridge.Promise
|
|
21
11
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
22
12
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
23
13
|
import com.facebook.react.bridge.ReactMethod
|
|
24
14
|
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
|
|
15
|
+
import com.streamvideo.reactnative.util.PiPHelper
|
|
25
16
|
import com.streamvideo.reactnative.util.RingtoneUtil
|
|
26
17
|
|
|
27
18
|
|
|
@@ -36,43 +27,11 @@ class StreamVideoReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
36
27
|
|
|
37
28
|
override fun initialize() {
|
|
38
29
|
super.initialize()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
RCTDeviceEventEmitter::class.java
|
|
43
|
-
).emit(PIP_CHANGE_EVENT, isInPictureInPictureMode)
|
|
44
|
-
// inform the activity
|
|
45
|
-
if (isInPictureInPictureMode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasPiPSupport()) {
|
|
46
|
-
(reactApplicationContext.currentActivity as? ReactActivity)?.let { activity ->
|
|
47
|
-
try {
|
|
48
|
-
val params = getPiPParams(activity)
|
|
49
|
-
val aspect =
|
|
50
|
-
if (newConfig.orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
|
|
51
|
-
Rational(9, 16)
|
|
52
|
-
} else {
|
|
53
|
-
Rational(16, 9)
|
|
54
|
-
}
|
|
55
|
-
params.setAspectRatio(aspect)
|
|
56
|
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
57
|
-
// this platform doesn't support autoEnterEnabled
|
|
58
|
-
// so we manually enter here
|
|
59
|
-
activity.enterPictureInPictureMode(params.build())
|
|
60
|
-
} else {
|
|
61
|
-
activity.setPictureInPictureParams(params.build())
|
|
62
|
-
}
|
|
63
|
-
// NOTE: workaround - on PiP mode, android goes to "paused but can render" state
|
|
64
|
-
// RN pauses rendering in paused mode, so we instruct it to resume here
|
|
65
|
-
reactApplicationContext?.onHostResume(activity)
|
|
66
|
-
} catch (e: IllegalStateException) {
|
|
67
|
-
Log.d(
|
|
68
|
-
NAME,
|
|
69
|
-
"Skipping Picture-in-Picture mode. Its not enabled for activity"
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
30
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
31
|
+
StreamVideoReactNative.addPipListener { isInPictureInPictureMode, newConfig ->
|
|
32
|
+
PiPHelper.onPiPChange(reactApplicationContext, isInPictureInPictureMode, newConfig)
|
|
73
33
|
}
|
|
74
34
|
}
|
|
75
|
-
|
|
76
35
|
val filter = IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)
|
|
77
36
|
reactApplicationContext.registerReceiver(powerReceiver, filter)
|
|
78
37
|
}
|
|
@@ -93,9 +52,11 @@ class StreamVideoReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
93
52
|
|
|
94
53
|
@ReactMethod
|
|
95
54
|
fun isInPiPMode(promise: Promise) {
|
|
96
|
-
|
|
97
|
-
reactApplicationContext
|
|
98
|
-
|
|
55
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
56
|
+
promise.resolve(PiPHelper.isInPiPMode(reactApplicationContext))
|
|
57
|
+
} else {
|
|
58
|
+
promise.resolve(false)
|
|
59
|
+
}
|
|
99
60
|
}
|
|
100
61
|
|
|
101
62
|
@Suppress("UNUSED_PARAMETER")
|
|
@@ -117,23 +78,8 @@ class StreamVideoReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
117
78
|
|
|
118
79
|
@ReactMethod
|
|
119
80
|
fun canAutoEnterPipMode(value: Boolean) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
reactApplicationContext.currentActivity?.let { activity ->
|
|
123
|
-
try {
|
|
124
|
-
if (value) {
|
|
125
|
-
activity.setPictureInPictureParams(getPiPParams(activity).build())
|
|
126
|
-
// NOTE: for SDK_INT < Build.VERSION_CODES.S
|
|
127
|
-
// onUserLeaveHint from Activity is used, SDK cant directly use it
|
|
128
|
-
// onUserLeaveHint will call the PiP listener and we call enterPictureInPictureMode there
|
|
129
|
-
} else {
|
|
130
|
-
val params = PictureInPictureParams.Builder()
|
|
131
|
-
params.setAutoEnterEnabled(false)
|
|
132
|
-
activity.setPictureInPictureParams(params.build())
|
|
133
|
-
}
|
|
134
|
-
} catch (e: IllegalStateException) {
|
|
135
|
-
Log.d(NAME, "Skipping Picture-in-Picture mode. Its not enabled for activity")
|
|
136
|
-
}
|
|
81
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
82
|
+
PiPHelper.canAutoEnterPipMode(reactApplicationContext, value)
|
|
137
83
|
}
|
|
138
84
|
}
|
|
139
85
|
|
|
@@ -241,58 +187,7 @@ class StreamVideoReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
241
187
|
}
|
|
242
188
|
}
|
|
243
189
|
|
|
244
|
-
private fun hasPiPSupport(): Boolean {
|
|
245
|
-
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && reactApplicationContext.packageManager.hasSystemFeature(
|
|
246
|
-
PackageManager.FEATURE_PICTURE_IN_PICTURE
|
|
247
|
-
)
|
|
248
|
-
) {
|
|
249
|
-
val appOps =
|
|
250
|
-
reactApplicationContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
|
|
251
|
-
val packageName = reactApplicationContext.packageName
|
|
252
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
253
|
-
appOps.unsafeCheckOpNoThrow(
|
|
254
|
-
AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
|
|
255
|
-
Process.myUid(),
|
|
256
|
-
packageName
|
|
257
|
-
) == AppOpsManager.MODE_ALLOWED
|
|
258
|
-
} else {
|
|
259
|
-
appOps.checkOpNoThrow(
|
|
260
|
-
AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
|
|
261
|
-
Process.myUid(),
|
|
262
|
-
packageName
|
|
263
|
-
) == AppOpsManager.MODE_ALLOWED
|
|
264
|
-
}
|
|
265
|
-
} else {
|
|
266
|
-
false
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
@RequiresApi(Build.VERSION_CODES.O)
|
|
271
|
-
private fun getPiPParams(activity: Activity): PictureInPictureParams.Builder {
|
|
272
|
-
val currentOrientation = activity.resources.configuration.orientation
|
|
273
|
-
|
|
274
|
-
val aspect =
|
|
275
|
-
if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
|
|
276
|
-
Rational(9, 16)
|
|
277
|
-
} else {
|
|
278
|
-
Rational(16, 9)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
val params = PictureInPictureParams.Builder()
|
|
282
|
-
params.setAspectRatio(aspect).apply {
|
|
283
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
284
|
-
setAutoEnterEnabled(true)
|
|
285
|
-
}
|
|
286
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
287
|
-
setTitle("Video Player")
|
|
288
|
-
setSeamlessResizeEnabled(false)
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return params
|
|
292
|
-
}
|
|
293
|
-
|
|
294
190
|
companion object {
|
|
295
191
|
private const val NAME = "StreamVideoReactNative"
|
|
296
|
-
private const val PIP_CHANGE_EVENT = NAME + "_PIP_CHANGE_EVENT"
|
|
297
192
|
}
|
|
298
193
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
package com.streamvideo.reactnative.util
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.AppOpsManager
|
|
5
|
+
import android.app.PictureInPictureParams
|
|
6
|
+
import android.content.Context
|
|
7
|
+
import android.content.pm.ActivityInfo
|
|
8
|
+
import android.content.pm.PackageManager
|
|
9
|
+
import android.content.res.Configuration
|
|
10
|
+
import android.os.Build
|
|
11
|
+
import android.os.Process
|
|
12
|
+
import android.util.Log
|
|
13
|
+
import android.util.Rational
|
|
14
|
+
import androidx.annotation.RequiresApi
|
|
15
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
16
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
|
|
17
|
+
import com.streamvideo.reactnative.StreamVideoReactNative
|
|
18
|
+
|
|
19
|
+
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
20
|
+
object PiPHelper {
|
|
21
|
+
private const val NAME = "StreamVideoReactNative"
|
|
22
|
+
private const val PIP_CHANGE_EVENT = NAME + "_PIP_CHANGE_EVENT"
|
|
23
|
+
|
|
24
|
+
fun onPiPChange(
|
|
25
|
+
reactApplicationContext: ReactApplicationContext,
|
|
26
|
+
isInPictureInPictureMode: Boolean,
|
|
27
|
+
newConfig: Configuration
|
|
28
|
+
) {
|
|
29
|
+
// Send event to JavaScript
|
|
30
|
+
reactApplicationContext.getJSModule(
|
|
31
|
+
RCTDeviceEventEmitter::class.java
|
|
32
|
+
).emit(PIP_CHANGE_EVENT, isInPictureInPictureMode)
|
|
33
|
+
// inform activity
|
|
34
|
+
reactApplicationContext.currentActivity?.let { activity ->
|
|
35
|
+
if (isInPictureInPictureMode && hasPiPSupport(reactApplicationContext)) {
|
|
36
|
+
try {
|
|
37
|
+
val params = getPiPParams(activity)
|
|
38
|
+
val aspect =
|
|
39
|
+
if (newConfig.orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
|
|
40
|
+
Rational(9, 16)
|
|
41
|
+
} else {
|
|
42
|
+
Rational(16, 9)
|
|
43
|
+
}
|
|
44
|
+
params.setAspectRatio(aspect)
|
|
45
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
46
|
+
// this platform doesn't support autoEnterEnabled
|
|
47
|
+
// so we manually enter here
|
|
48
|
+
activity.enterPictureInPictureMode(params.build())
|
|
49
|
+
} else {
|
|
50
|
+
activity.setPictureInPictureParams(params.build())
|
|
51
|
+
}
|
|
52
|
+
// NOTE: workaround - on PiP mode, android goes to "paused but can render" state
|
|
53
|
+
// RN pauses rendering in paused mode, so we instruct it to resume here
|
|
54
|
+
reactApplicationContext.onHostResume(activity)
|
|
55
|
+
} catch (e: IllegalStateException) {
|
|
56
|
+
Log.d(
|
|
57
|
+
NAME,
|
|
58
|
+
"Skipping Picture-in-Picture mode. Its not enabled for activity"
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fun canAutoEnterPipMode(reactApplicationContext: ReactApplicationContext, value: Boolean) {
|
|
66
|
+
StreamVideoReactNative.canAutoEnterPictureInPictureMode = value
|
|
67
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return
|
|
68
|
+
reactApplicationContext.currentActivity?.let { activity ->
|
|
69
|
+
try {
|
|
70
|
+
val builder = getPiPParams(activity)
|
|
71
|
+
if (value) {
|
|
72
|
+
activity.setPictureInPictureParams(builder.build())
|
|
73
|
+
// NOTE: for SDK_INT < Build.VERSION_CODES.S
|
|
74
|
+
// onUserLeaveHint from Activity is used, SDK cant directly use it
|
|
75
|
+
// onUserLeaveHint will call the PiP listener and we call enterPictureInPictureMode there
|
|
76
|
+
} else {
|
|
77
|
+
val params = PictureInPictureParams.Builder()
|
|
78
|
+
params.setAutoEnterEnabled(false)
|
|
79
|
+
activity.setPictureInPictureParams(params.build())
|
|
80
|
+
}
|
|
81
|
+
} catch (e: IllegalStateException) {
|
|
82
|
+
Log.d(NAME, "Skipping Picture-in-Picture mode. Its not enabled for activity")
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fun isInPiPMode(reactApplicationContext: ReactApplicationContext): Boolean? {
|
|
88
|
+
return reactApplicationContext.currentActivity?.isInPictureInPictureMode
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private fun getPiPParams(activity: Activity): PictureInPictureParams.Builder {
|
|
92
|
+
val currentOrientation = activity.resources.configuration.orientation
|
|
93
|
+
val aspect =
|
|
94
|
+
if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
|
|
95
|
+
Rational(9, 16)
|
|
96
|
+
} else {
|
|
97
|
+
Rational(16, 9)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
val params = PictureInPictureParams.Builder()
|
|
101
|
+
params.setAspectRatio(aspect).apply {
|
|
102
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
103
|
+
setAutoEnterEnabled(true)
|
|
104
|
+
}
|
|
105
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
106
|
+
setTitle("Video Player")
|
|
107
|
+
setSeamlessResizeEnabled(false)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return params
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private fun hasPiPSupport(reactApplicationContext: ReactApplicationContext): Boolean {
|
|
114
|
+
return if (reactApplicationContext.packageManager.hasSystemFeature(
|
|
115
|
+
PackageManager.FEATURE_PICTURE_IN_PICTURE
|
|
116
|
+
)
|
|
117
|
+
) {
|
|
118
|
+
val appOps =
|
|
119
|
+
reactApplicationContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
|
|
120
|
+
val packageName = reactApplicationContext.packageName
|
|
121
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
122
|
+
appOps.unsafeCheckOpNoThrow(
|
|
123
|
+
AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
|
|
124
|
+
Process.myUid(),
|
|
125
|
+
packageName
|
|
126
|
+
) == AppOpsManager.MODE_ALLOWED
|
|
127
|
+
} else {
|
|
128
|
+
appOps.checkOpNoThrow(
|
|
129
|
+
AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
|
|
130
|
+
Process.myUid(),
|
|
131
|
+
packageName
|
|
132
|
+
) == AppOpsManager.MODE_ALLOWED
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
false
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
package/dist/commonjs/version.js
CHANGED
package/dist/module/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '1.9.
|
|
1
|
+
export const version = '1.9.21';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "1.9.
|
|
1
|
+
export declare const version = "1.9.21";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.9.
|
|
1
|
+
export const version = '1.9.21';
|