Skip to content

Commit 7bc9eac

Browse files
authored
fix(ui): await screen share service creation (#1139)
* await screen share service creation * add proper result handling * changelog * improve parcerable handling * pip sorting tweak * changelog
1 parent 27673d5 commit 7bc9eac

File tree

19 files changed

+170
-49
lines changed

19 files changed

+170
-49
lines changed

melos.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ command:
2222
device_info_plus: ^12.1.0
2323
share_plus: ^11.0.0
2424
stream_chat_flutter: ^9.17.0
25-
stream_webrtc_flutter: ^2.2.2
25+
stream_webrtc_flutter: ^2.2.3
2626
stream_video: ^1.2.0
2727
stream_video_flutter: ^1.2.0
2828
stream_video_noise_cancellation: ^1.2.0

packages/stream_video/lib/src/sorting/call_participant_sorting_presets.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,16 @@ mixin CallParticipantSortingPresets {
3737
byRole(['admin', 'host', 'speaker']),
3838
],
3939
);
40+
41+
/// The sorting preset for PiP layout.
42+
/// For picture in picture we don't rely on visibility as it's a separate view not related to the main one.
43+
static final pictureInPicture = combineComparators<CallParticipantState>([
44+
pinned,
45+
screenSharing,
46+
dominantSpeaker,
47+
speaking,
48+
byVideoIngressSource(),
49+
publishingVideo,
50+
publishingAudio,
51+
]);
4052
}

packages/stream_video/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ dependencies:
3131
rxdart: ^0.28.0
3232
sdp_transform: ^0.3.2
3333
state_notifier: ^1.0.0
34-
stream_webrtc_flutter: ^2.2.2
34+
stream_webrtc_flutter: ^2.2.3
3535
synchronized: ^3.1.0
3636
system_info2: ^4.0.0
3737
tart: ^0.6.0

packages/stream_video_filters/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ dependencies:
1515
sdk: flutter
1616
plugin_platform_interface: ^2.0.2
1717
stream_video: ^1.2.0
18-
stream_webrtc_flutter: ^2.2.2
18+
stream_webrtc_flutter: ^2.2.3
1919

2020
dev_dependencies:
2121
flutter_lints: ^6.0.0

packages/stream_video_flutter/CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
## Upcoming
22

3-
🐞 Fixed
3+
### ✅ Added
4+
* Added default sorting preset for Picture-in-Picture mode to prioritize the most relevant participant.
5+
6+
### 🐞 Fixed
7+
* [Android] Fixed screen share notification tap not opening the app.
8+
* [Android] Improved screen sharing foreground service reliability:
9+
- Added proper synchronization to ensure the foreground service is fully started before initiating media projection.
10+
- `startScreenSharingNotificationService` now returns a boolean indicating success/failure, preventing screen share attempts when the service fails to start.
411
* [iOS/macOS] Fixed crash when VoIP push is received before Flutter fully initializes from the terminated state.
512

613
## 1.2.0

packages/stream_video_flutter/android/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ android {
3434

3535
defaultConfig {
3636
minSdkVersion 19
37+
consumerProguardFiles 'consumer-rules.pro'
3738
}
3839

3940
dependencies {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-keepclassmembers class * implements android.os.Parcelable {
2+
public static final ** CREATOR;
3+
}
4+
5+
# Keep the specific notification payload classes
6+
-keep class io.getstream.video.flutter.stream_video_flutter.service.notification.NotificationPayload { *; }
7+
-keep class io.getstream.video.flutter.stream_video_flutter.service.notification.NotificationOptions { *; }
8+
-keep class io.getstream.video.flutter.stream_video_flutter.service.notification.NotificationContent { *; }
9+
-keep class io.getstream.video.flutter.stream_video_flutter.service.notification.NotificationAvatar { *; }
10+
11+
# Keep all classes that use @Parcelize annotation
12+
-keep @kotlinx.parcelize.Parcelize class * { *; }
13+

packages/stream_video_flutter/android/src/main/kotlin/io/getstream/video/flutter/stream_video_flutter/service/ServiceManager.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,13 @@ class ServiceManagerImpl(
3333
val serviceKey = "$callCid-$type"
3434

3535
if (activeServices.containsKey(serviceKey)) {
36-
logger.w { "[start] Service for callCid: $callCid, type: $type already running." }
37-
return true
36+
if (type == ServiceType.screenSharing && !StreamScreenShareService.isServiceRunning(callCid)) {
37+
logger.w { "[start] Service for callCid: $callCid was in activeServices but not in foreground mode. Retrying." }
38+
activeServices.remove(serviceKey)
39+
} else {
40+
logger.w { "[start] Service for callCid: $callCid, type: $type already running." }
41+
return true
42+
}
3843
}
3944
try {
4045
val nIntent = when(type){
@@ -176,7 +181,14 @@ class ServiceManagerImpl(
176181
}
177182

178183
val serviceKey = "$actualCallCid-$type"
179-
val running = activeServices.containsKey(serviceKey)
184+
val inActiveServices = activeServices.containsKey(serviceKey)
185+
186+
val running = if (type == ServiceType.screenSharing) {
187+
inActiveServices && StreamScreenShareService.isServiceRunning(actualCallCid)
188+
} else {
189+
inActiveServices
190+
}
191+
180192
logger.d { "[isRunning] callCid: $actualCallCid, type: $type. Result: $running" }
181193
return running
182194
}

packages/stream_video_flutter/android/src/main/kotlin/io/getstream/video/flutter/stream_video_flutter/service/StreamCallService.kt

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import android.content.pm.ServiceInfo
1010
import android.os.Build
1111
import android.os.IBinder
1212
import androidx.core.content.ContextCompat
13+
import androidx.core.content.IntentCompat
1314
import io.getstream.log.taggedLogger
1415
import io.getstream.video.flutter.stream_video_flutter.service.notification.NotificationPayload
1516
import io.getstream.video.flutter.stream_video_flutter.service.notification.StreamNotificationBuilder
@@ -177,14 +178,11 @@ open class StreamCallService : Service() {
177178
}
178179

179180
private fun getPayloadFromIntent(intent: Intent): NotificationPayload? {
180-
@Suppress("DEPRECATION")
181-
val payload = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
182-
intent.getParcelableExtra("notificationPayload", NotificationPayload::class.java)
183-
} else {
184-
intent.getParcelableExtra("notificationPayload")
185-
}
186-
187-
return payload
181+
return IntentCompat.getParcelableExtra(
182+
intent,
183+
"notificationPayload",
184+
NotificationPayload::class.java
185+
)
188186
}
189187

190188
private fun startNewCall(callCid: String, payload: NotificationPayload, startId: Int) {

packages/stream_video_flutter/android/src/main/kotlin/io/getstream/video/flutter/stream_video_flutter/service/StreamScreenShareService.kt

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import android.app.NotificationManager
44
import android.app.Service
55
import android.content.Context
66
import android.content.Intent
7-
import android.os.Build
87
import android.os.IBinder
8+
import androidx.core.content.IntentCompat
99
import io.getstream.log.taggedLogger
1010
import io.getstream.video.flutter.stream_video_flutter.R
1111
import io.getstream.video.flutter.stream_video_flutter.service.notification.NotificationPayload
@@ -107,12 +107,11 @@ internal class StreamScreenShareService : Service() {
107107
}
108108

109109
private fun getPayloadFromIntent(intent: Intent): NotificationPayload? {
110-
@Suppress("DEPRECATION")
111-
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
112-
intent.getParcelableExtra("notificationPayload", NotificationPayload::class.java)
113-
} else {
114-
intent.getParcelableExtra("notificationPayload")
115-
}
110+
return IntentCompat.getParcelableExtra(
111+
intent,
112+
"notificationPayload",
113+
NotificationPayload::class.java
114+
)
116115
}
117116

118117
private fun startNewScreenShare(callCid: String, payload: NotificationPayload, startId: Int) {
@@ -132,12 +131,15 @@ internal class StreamScreenShareService : Service() {
132131
if (activeScreenShares.size == 1) {
133132
try {
134133
super.startForeground(notificationId, notification)
134+
markServiceAsStarted(callCid)
135135
logger.i { "[startNewScreenShare] Service started in foreground for screen share (callCid: $callCid), notificationId: $notificationId" }
136136
} catch (e: Exception) {
137137
logger.e(e) { "[startNewScreenShare] Exception starting foreground for screen share $callCid. Error: ${e.message}" }
138138
activeScreenShares.remove(callCid)
139139
}
140140
} else {
141+
// Service is already in foreground, just mark this callCid as ready
142+
markServiceAsStarted(callCid)
141143
notificationManager.notify(notificationId, notification)
142144
logger.i { "[startNewScreenShare] Additional screen share notification for callCid: $callCid, notificationId: $notificationId" }
143145
}
@@ -156,6 +158,7 @@ internal class StreamScreenShareService : Service() {
156158
logger.i { "[stopScreenShare] Attempting to stop screen share for callCid: $callCid. Active shares: ${activeScreenShares.size}" }
157159
val screenShareData = activeScreenShares.remove(callCid)
158160
if (screenShareData != null) {
161+
markServiceAsStopped(callCid)
159162
notificationManager.cancel(screenShareData.notificationId)
160163
logger.i { "[stopScreenShare] Cancelled notification and removed screen share for callCid: $callCid. Remaining: ${activeScreenShares.size}" }
161164
} else {
@@ -174,6 +177,7 @@ internal class StreamScreenShareService : Service() {
174177
super.onDestroy()
175178
logger.i { "[onDestroy] Service destroying. Clearing ${activeScreenShares.size} screen shares and notifications." }
176179
activeScreenShares.values.forEach { data ->
180+
markServiceAsStopped(data.callCid)
177181
notificationManager.cancel(data.notificationId)
178182
}
179183
activeScreenShares.clear()
@@ -202,5 +206,22 @@ internal class StreamScreenShareService : Service() {
202206
internal const val ACTION_UPDATE = "UPDATE"
203207
internal const val ACTION_STOP_SPECIFIC_CALL = "STOP_SPECIFIC_CALL"
204208
internal const val TRIGGER_SHARE_SCREEN = "SHARE_SCREEN"
209+
210+
private val startedServicesCallCids = mutableSetOf<String>()
211+
212+
@Synchronized
213+
internal fun isServiceRunning(callCid: String): Boolean {
214+
return startedServicesCallCids.contains(callCid)
215+
}
216+
217+
@Synchronized
218+
private fun markServiceAsStarted(callCid: String) {
219+
startedServicesCallCids.add(callCid)
220+
}
221+
222+
@Synchronized
223+
private fun markServiceAsStopped(callCid: String) {
224+
startedServicesCallCids.remove(callCid)
225+
}
205226
}
206227
}

0 commit comments

Comments
 (0)