Skip to content

Commit b3a7f14

Browse files
authored
feat(ui): Allow video filters to be set before video track is created (#1035)
* Allow video filters to be set before video track is created * changelog * tweak
1 parent d94c6fa commit b3a7f14

File tree

10 files changed

+126
-47
lines changed

10 files changed

+126
-47
lines changed

dogfooding/lib/app/app_content.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ class _StreamDogFoodingAppContentState
154154
final extra = (
155155
call: call,
156156
connectOptions: null,
157+
effectsManager: null,
157158
);
158159

159160
_router.push(CallRoute($extra: extra).location, extra: extra);
@@ -179,6 +180,7 @@ class _StreamDogFoodingAppContentState
179180
final extra = (
180181
call: callToJoin,
181182
connectOptions: null,
183+
effectsManager: null,
182184
);
183185

184186
_router.push(CallRoute($extra: extra).location, extra: extra);
@@ -193,6 +195,7 @@ class _StreamDogFoodingAppContentState
193195
final extra = (
194196
call: call,
195197
connectOptions: null,
198+
effectsManager: null,
196199
);
197200

198201
_router.push(CallRoute($extra: extra).location, extra: extra);

dogfooding/lib/router/routes.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,13 @@ class LobbyRoute extends GoRouteData {
4545
Widget build(BuildContext context, GoRouterState state) {
4646
return LobbyScreen(
4747
call: $extra,
48-
onJoinCallPressed: (connectOptions) {
48+
onJoinCallPressed: (connectOptions, effectsManager) {
4949
// Navigate to the call screen.
5050
CallRoute(
5151
$extra: (
5252
call: $extra,
5353
connectOptions: connectOptions,
54+
effectsManager: effectsManager,
5455
),
5556
).replace(context);
5657
},
@@ -79,13 +80,15 @@ class CallRoute extends GoRouteData {
7980
final ({
8081
Call call,
8182
CallConnectOptions? connectOptions,
83+
StreamVideoEffectsManager? effectsManager,
8284
}) $extra;
8385

8486
@override
8587
Widget build(BuildContext context, GoRouterState state) {
8688
return CallScreen(
8789
call: $extra.call,
8890
connectOptions: $extra.connectOptions,
91+
videoEffectsManager: $extra.effectsManager,
8992
);
9093
}
9194
}

dogfooding/lib/router/routes.g.dart

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dogfooding/lib/screens/call_screen.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,21 @@ class CallScreen extends StatefulWidget {
3131
super.key,
3232
required this.call,
3333
this.connectOptions,
34+
this.videoEffectsManager,
3435
});
3536

3637
final Call call;
3738
final CallConnectOptions? connectOptions;
39+
final StreamVideoEffectsManager? videoEffectsManager;
3840

3941
@override
4042
State<CallScreen> createState() => _CallScreenState();
4143
}
4244

4345
class _CallScreenState extends State<CallScreen> {
4446
late final _userChatRepo = locator.get<UserChatRepository>();
45-
late final _videoEffectsManager = StreamVideoEffectsManager(widget.call);
47+
late final _videoEffectsManager =
48+
widget.videoEffectsManager ?? StreamVideoEffectsManager(widget.call);
4649
late StreamSubscription<SpeakingWhileMutedState> _speechSubscription;
4750

4851
Channel? _channel;
@@ -78,6 +81,7 @@ class _CallScreenState extends State<CallScreen> {
7881
_speakingWhileMutedRecognition.dispose();
7982
widget.call.leave();
8083
_userChatRepo.disconnectUser();
84+
_videoEffectsManager.dispose();
8185
super.dispose();
8286
}
8387

dogfooding/lib/screens/home_screen.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class _HomeScreenState extends State<HomeScreen> {
116116
CallRoute($extra: (
117117
call: _call!,
118118
connectOptions: null,
119+
effectsManager: null,
119120
)).push(context);
120121
} else {
121122
LobbyRoute($extra: _call!).push(context);

dogfooding/lib/screens/lobby_screen.dart

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ class LobbyScreen extends StatefulWidget {
1616
required this.call,
1717
});
1818

19-
final ValueChanged<CallConnectOptions> onJoinCallPressed;
19+
final Function(CallConnectOptions, StreamVideoEffectsManager)
20+
onJoinCallPressed;
2021
final Call call;
2122

2223
@override
@@ -26,8 +27,16 @@ class LobbyScreen extends StatefulWidget {
2627
class _LobbyScreenState extends State<LobbyScreen> {
2728
RtcLocalAudioTrack? _microphoneTrack;
2829
RtcLocalCameraTrack? _cameraTrack;
30+
bool _blurEnabled = false;
2931

3032
final _userAuthController = locator.get<UserAuthController>();
33+
late StreamVideoEffectsManager _videoEffectsManager;
34+
35+
@override
36+
void initState() {
37+
super.initState();
38+
_videoEffectsManager = StreamVideoEffectsManager(widget.call);
39+
}
3140

3241
void joinCallPressed() {
3342
var options = const CallConnectOptions();
@@ -46,7 +55,7 @@ class _LobbyScreenState extends State<LobbyScreen> {
4655
);
4756
}
4857

49-
widget.onJoinCallPressed(options);
58+
widget.onJoinCallPressed(options, _videoEffectsManager);
5059
}
5160

5261
@override
@@ -115,7 +124,41 @@ class _LobbyScreenState extends State<LobbyScreen> {
115124
StreamLobbyVideo(
116125
call: widget.call,
117126
onMicrophoneTrackSet: (track) => _microphoneTrack = track,
118-
onCameraTrackSet: (track) => _cameraTrack = track,
127+
onCameraTrackSet: (track) {
128+
_cameraTrack = track;
129+
130+
if (track != null && _blurEnabled) {
131+
_videoEffectsManager.applyBackgroundBlurFilter(
132+
BlurIntensity.medium,
133+
track: track,
134+
);
135+
}
136+
},
137+
additionalActionsBuilder: (context, call) {
138+
return [
139+
CallControlOption(
140+
icon: _blurEnabled
141+
? const Icon(Icons.blur_on)
142+
: const Icon(Icons.blur_off),
143+
onPressed: () async {
144+
setState(() {
145+
_blurEnabled = !_blurEnabled;
146+
});
147+
148+
if (_blurEnabled) {
149+
_videoEffectsManager.applyBackgroundBlurFilter(
150+
BlurIntensity.medium,
151+
track: _cameraTrack,
152+
);
153+
} else {
154+
_videoEffectsManager.disableAllFilters(
155+
track: _cameraTrack,
156+
);
157+
}
158+
},
159+
),
160+
];
161+
},
119162
),
120163
const SizedBox(height: 24),
121164
Container(

packages/stream_video_flutter/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
## Unreleased
22

3+
🐞 Fixed
4+
* Fixed an issue where video filters were cleared after toggling the camera.
5+
36
✅ Added
7+
* Added support for setting video filters before the video track is created by listening for local participant state changes and applying the filters once the video is enabled.
8+
* Added support for setting video filters on a specific video track before the local participant is available — useful for scenarios like lobby previews with a temporary video track.
49
* Introduced the `reconnectTimeout` option in `CallPreferences`, allowing you to set the maximum duration the SDK will attempt to reconnect to a call before giving up.
510

611
🔄 Changed

packages/stream_video_flutter/lib/src/call_screen/lobby_video.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class StreamLobbyVideo extends StatefulWidget {
1818
this.onMicrophoneTrackSet,
1919
this.onCameraTrackSet,
2020
this.streamVideo,
21+
this.additionalActionsBuilder,
2122
});
2223

2324
/// Represents a call.
@@ -32,6 +33,8 @@ class StreamLobbyVideo extends StatefulWidget {
3233
final FutureOr<void> Function(RtcLocalAudioTrack?)? onMicrophoneTrackSet;
3334
final FutureOr<void> Function(RtcLocalCameraTrack?)? onCameraTrackSet;
3435

36+
final List<Widget> Function(BuildContext, Call)? additionalActionsBuilder;
37+
3538
/// An instance of [StreamVideo].
3639
///
3740
/// If not provided, it will be obtained via StreamVideo.instance
@@ -188,6 +191,8 @@ class _StreamLobbyVideoState extends State<StreamLobbyVideo> {
188191
cameraEnabled ? null : theme.optionOffBackgroundColor,
189192
onPressed: toggleCamera,
190193
),
194+
if (widget.additionalActionsBuilder != null)
195+
...widget.additionalActionsBuilder!(context, widget.call),
191196
],
192197
),
193198
],

packages/stream_video_flutter/lib/src/call_screen/lobby_view.dart

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -70,34 +70,6 @@ class _StreamLobbyViewState extends State<StreamLobbyView> {
7070

7171
bool get hasMicrophoneEnabled => _microphoneTrack != null;
7272

73-
Future<void> toggleCamera() async {
74-
if (hasCameraEnabled) {
75-
await _cameraTrack?.stop();
76-
return setState(() => _cameraTrack = null);
77-
}
78-
79-
try {
80-
final cameraTrack = await RtcLocalTrack.camera();
81-
return setState(() => _cameraTrack = cameraTrack);
82-
} catch (e) {
83-
_logger.w(() => 'Error creating camera track: $e');
84-
}
85-
}
86-
87-
Future<void> toggleMicrophone() async {
88-
if (hasMicrophoneEnabled) {
89-
await _microphoneTrack?.stop();
90-
return setState(() => _microphoneTrack = null);
91-
}
92-
93-
try {
94-
final microphoneTrack = await RtcLocalTrack.audio();
95-
return setState(() => _microphoneTrack = microphoneTrack);
96-
} catch (e) {
97-
_logger.w(() => 'Error creating microphone track: $e');
98-
}
99-
}
100-
10173
void onJoinCallPressed() {
10274
var options = const CallConnectOptions();
10375

0 commit comments

Comments
 (0)