Skip to content

Commit c9bffdb

Browse files
authored
feat: PiP support for iOS (#712)
* PiP support for iOS * fix * cleanup * webrtc bump * screen sharing plugin added (#717) * screen sharing plugin added * fixes * tweaks * PR tweaks
1 parent 9acfa8a commit c9bffdb

File tree

53 files changed

+2422
-107
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2422
-107
lines changed

development.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@
5353
- [x] Bluetooth fixes (check support of BT media devices in flutter)
5454
- [x] Switch the earpiece/microphone button *(depends on "Bluetooth fixes")*
5555
- [x] Align custom event support to other SDKs
56+
- [x] Picture-in-picture
57+
- - [x] Android
58+
- - [x] iOS
59+
- [x] Call stats component
60+
- [x] Transcription
61+
62+
### 0.5.0 milestone
63+
- [ ] Documentation parity
64+
- - [ ] UI components
65+
- - [ ] Cookbook
66+
- - [ ] Advanced guides
5667
- [ ] Tap to focus (flutter_webrtc)
5768
- [ ] Video filters / audio filters
5869
- [ ] Local audio levels (maybe from webrtc)
@@ -61,18 +72,8 @@
6172
- - [ ] stream_video_flutter (75%)
6273
- - [ ] stream_video_push_notification
6374
- - [ ] Coverage check for PRs
64-
- [ ] Documentation parity
65-
- - [ ] UI components
66-
- - [ ] Cookbook
67-
- - [ ] Advanced guides
68-
69-
### 0.5.0 milestone
70-
- [ ] Picture-in-picture
71-
- - [x] Android
72-
- - [ ] iOS
7375
- [ ] Dynascale 2.0
7476
- [ ] SFU switching
7577
- [ ] Buttons to simulate ICE restarts / SFU switching
7678
- [ ] Proximity button support
77-
- [x] Call stats component
78-
- [ ] Transcription
79+
- [ ] Reconnection v2

docusaurus/docs/Flutter/05-advanced/03-screen-sharing.mdx

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ If you use our UI components you can also add `ToggleScreenShareOption` as one o
4646
When the method is invoked, ReplayKit will ask for the user's consent that their screen will be shared. Only after the permission is granted, the screensharing starts.
4747

4848
#### Broadcasting
49-
In most cases, you would need to share your screen while you are in the background, to be able to open other apps. For this, you need to create a Broadcast Upload Extension.
49+
In most cases, you would need to share your screen while the app is in the background, to be able to open other apps. For this, you need to create a Broadcast Upload Extension.
5050

5151
##### Toggle screen sharing with broadcast mode
5252

53-
If you want to start screen sharing in broadcast mode on iOS you will need to toggle on using `ScreenShareConstraints`. Just set the flag to true inside `ToggleScreenShareOption` or when you use `call.setScreenShareEnabled()` directly.
53+
If you want to start screen sharing in broadcast mode on iOS you will need to toggle it by setting `useiOSBroadcastExtension` flag to true in `ScreenShareConstraints`. You can set the constraints inside `ToggleScreenShareOption` or when you use `call.setScreenShareEnabled()` directly.
5454

5555
```dart
5656
const constraints = ScreenShareConstraints(
@@ -72,6 +72,8 @@ call.setScreenShareEnabled(enabled: true, constraints: constraints);
7272

7373
##### Add Broadcast Upload Extension
7474

75+
iOS requires the use of Broadcast Upload Extensions to facilitate screen sharing when your app is in the background. This extension provides the necessary framework to handle capturing and broadcasting the screen content.
76+
7577
Now add the extension, without UI, to your project in Xcode:
7678

7779
![Screen sharing dashboard](../assets/advanced_assets/broadcast-extension.png)
@@ -85,17 +87,24 @@ After you create the extension, there should be a class called `SampleHandler`,
8587

8688
![Screen sharing dashboard](../assets/advanced_assets/broadcast-handler-implementation.png)
8789

88-
:::tip
89-
If Xcode cannot find the `stream_video_screen_sharing` module add this code block to your app's Podfile file:
90+
To have access to our Handler implementation add `stream_video_screen_sharing` package to your app's `pubspec.yaml` file:
91+
92+
```yaml
93+
dependencies:
94+
stream_video_screen_sharing: ^<latest_version>
95+
```
96+
97+
Then for native code to see it, add it as a dependency manually for the extension target in the Podfile file:
9098
9199
```Podfile
92-
target 'ScreenSharing' do
100+
target 'YOUR_EXTENSION_NAME' do
93101
use_frameworks!
94-
pod 'stream_video_flutter', :path => File.join('.symlinks', 'plugins', 'stream_video_flutter', 'ios')
102+
pod 'stream_video_screen_sharing', :path => File.join('.symlinks', 'plugins', 'stream_video_screen_sharing', 'ios')
95103
end
96104
```
97105

98-
Replace `ScreenSharing` with your extension name.
106+
:::note
107+
Replace `YOUR_EXTENSION_NAME` with the name of the extension you created.
99108
:::
100109

101110
##### Setup app groups
@@ -178,4 +187,22 @@ StreamBackgroundService.init(
178187
}
179188
},
180189
);
181-
```
190+
```
191+
192+
### Screen sharing settings
193+
194+
You can customize the screen sharing behavior by providing `ScreenShareConstraints` to the `ToggleScreenShareOption` widget or `setScreenShareEnabled()` method.
195+
196+
You can specify the following settings:
197+
198+
- `useiOSBroadcastExtension` - Set to `true` to enable broadcast mode on iOS.
199+
200+
- `captureScreenAudio` - Set to `true` to capture audio from the screen.
201+
202+
- `sourceId` - The device ID of an audio source, if you want to capture audio from a specific source.
203+
204+
- `maxFrameRate` - The maximum frame rate for the screen sharing video.
205+
206+
- `params` - The video parameters for the screen sharing video.
207+
208+
For `params` you can use one of our predefined presets in `RtcVideoParametersPresets`.

docusaurus/docs/Flutter/05-advanced/04-picture-in-picture.mdx

Lines changed: 84 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,19 @@ title: Picture in Picture (PiP)
66

77
Picture in picture (PIP) keeps the call running and visible while you navigate to other apps.
88

9-
:::info
10-
At the moment Picture in Picture is only supported on Android.
11-
:::
12-
139
### Enable Picture-in-Picture
14-
You can enable Picture in Picture by setting the `enablePictureInPicture` property to `true` in the `StreamCallContainer` or `StreamCallContent` widget.
10+
You can enable Picture in Picture by setting the `enablePictureInPicture` property to `true` in the `PictureInPictureConfiguration` provided to `StreamCallContainer` or `StreamCallContent` widget.
1511

1612
```dart
1713
StreamCallContainer(
1814
call: widget.call,
19-
enablePictureInPicture: true,
15+
pictureInPictureConfiguration: const PictureInPictureConfiguration(
16+
enablePictureInPicture: true,
17+
),
2018
)
2119
```
2220

23-
You can customize the widget rendered while app is in Picture-in-Picture mode by providing `callPictureInPictureBuilder` to `StreamCallContent`.
24-
25-
```dart
26-
StreamCallContainer(
27-
call: widget.call,
28-
callContentBuilder: (
29-
BuildContext context,
30-
Call call,
31-
CallState callState,
32-
) {
33-
return StreamCallContent(
34-
call: call,
35-
callState: callState,
36-
enablePictureInPicture: true,
37-
callPictureInPictureBuilder: (context, call, callState) {
38-
// YOUR CUSTOM WIDGET
39-
}
40-
);
41-
},
42-
);
43-
```
21+
## Android
4422

4523
### Android Configuration
4624
To enable Picture in Picture on Android, you need to add the following configuration to your `AndroidManifest.xml` file.
@@ -67,6 +45,85 @@ class MainActivity: FlutterActivity() {
6745
}
6846
```
6947

48+
### Android Customization
49+
50+
For Android, you can customize the widget rendered while app is in Picture-in-Picture mode by providing `callPictureInPictureBuilder` to `PictureInPictureConfiguration`.
51+
52+
```dart
53+
StreamCallContainer(
54+
call: widget.call,
55+
callContentBuilder: (
56+
BuildContext context,
57+
Call call,
58+
CallState callState,
59+
) {
60+
return StreamCallContent(
61+
call: call,
62+
callState: callState,
63+
pictureInPictureConfiguration: const PictureInPictureConfiguration(
64+
enablePictureInPicture: true,
65+
androidPiPConfiguration: AndroidPictureInPictureConfiguration(
66+
callPictureInPictureBuilder: (context, call, callState) {
67+
// YOUR CUSTOM WIDGET
68+
},
69+
)
70+
),
71+
);
72+
},
73+
);
74+
```
75+
76+
## iOS
77+
78+
### Local camera feed in Picture-in-Picture mode
79+
80+
By default iOS is not allowing usage of local camera feed when app is in the background. That includes Picture in picture mode and Split View mode. To enable it you need to request the [multitasking-camera-access](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_avfoundation_multitasking-camera-access) permission directly from Apple (this will change from iOS 18).
81+
If you already have the permission you have to enable local feed support in our PiP implementation by setting `ignoreLocalParticipantVideo` to `false`.
82+
83+
```dart
84+
StreamCallContainer(
85+
call: widget.call,
86+
callContentBuilder: (
87+
BuildContext context,
88+
Call call,
89+
CallState callState,
90+
) {
91+
return StreamCallContent(
92+
call: call,
93+
callState: callState,
94+
pictureInPictureConfiguration: const PictureInPictureConfiguration(
95+
enablePictureInPicture: true,
96+
iOSPiPConfiguration: IOSPictureInPictureConfiguration(
97+
ignoreLocalParticipantVideo: false,
98+
)
99+
),
100+
);
101+
},
102+
);
103+
```
104+
105+
### Enabling PiP support with custom call content widget
106+
107+
If you are not using our `StreamCallContent` and instead building custom call content widget you can still enable Picture in Picture mode by adding `StreamPictureInPictureUiKitView` anywhere in the widget tree. This widget will handle the Picture in Picture mode in iOS for you.
108+
109+
```dart
110+
StreamCallContainer(
111+
call: widget.call,
112+
callContentBuilder: (
113+
BuildContext context,
114+
Call call,
115+
CallState callState,
116+
) {
117+
return Stack(
118+
children: [
119+
StreamPictureInPictureUiKitView(call: call),
120+
// YOUR CUSTOM WIDGET
121+
],
122+
);
123+
},
124+
);
125+
```
126+
70127
Done. Now after leaving the app, you'll see that the call will be still alive in the background like the one below:
71128

72129
![Picture in Picture example](../assets/advanced_assets/pip_example.png)

dogfooding/ios/Podfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ end
3636

3737
target 'ScreenSharing' do
3838
use_frameworks!
39-
pod 'stream_video_flutter', :path => File.join('.symlinks', 'plugins', 'stream_video_flutter', 'ios')
39+
pod 'stream_video_screen_sharing', :path => File.join('.symlinks', 'plugins', 'stream_video_screen_sharing', 'ios')
4040
end
4141

4242
post_install do |installer|
@@ -60,4 +60,4 @@ post_install do |installer|
6060
end
6161
end
6262
end
63-
end
63+
end
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import ReplayKit
2-
import stream_video_flutter
2+
import stream_video_screen_sharing
33

44
class SampleHandler: BroadcastSampleHandler {}

dogfooding/lib/screens/call_screen.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,10 @@ class _CallScreenState extends State<CallScreen> {
147147
call: call,
148148
callState: callState,
149149
layoutMode: _currentLayoutMode,
150-
enablePictureInPicture: true,
150+
pictureInPictureConfiguration:
151+
const PictureInPictureConfiguration(
152+
enablePictureInPicture: true,
153+
),
151154
callParticipantsBuilder: (context, call, callState) {
152155
return Stack(
153156
children: [

dogfooding/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies:
3131
stream_chat_flutter: ^8.0.0
3232
stream_video_flutter: ^0.4.4
3333
stream_video_push_notification: ^0.4.4
34+
stream_video_screen_sharing: ^0.4.4
3435
uni_links: ^0.5.1
3536

3637
dev_dependencies:

packages/stream_video/lib/src/webrtc/rtc_manager.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import 'codecs_helper.dart' as codecs;
1313
import 'model/rtc_audio_bitrate_preset.dart';
1414
import 'model/rtc_tracks_info.dart';
1515
import 'model/rtc_video_encoding.dart';
16-
import 'model/rtc_video_parameters.dart';
1716
import 'peer_connection.dart';
1817
import 'rtc_parser.dart';
1918

packages/stream_video/lib/stream_video.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export 'src/utils/string.dart';
3232
export 'src/utils/subscriptions.dart';
3333
export 'src/webrtc/media/media_constraints.dart';
3434
export 'src/webrtc/model/rtc_video_dimension.dart';
35+
export 'src/webrtc/model/rtc_video_parameters.dart';
3536
export 'src/webrtc/peer_type.dart';
3637
export 'src/webrtc/rtc_media_device/rtc_media_device.dart';
3738
export 'src/webrtc/rtc_media_device/rtc_media_device_notifier.dart';
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file is generated. Do not manually edit.
22
/// Current package version.
33
const String streamVideoVersion = '0.4.4';
4-
const String androidWebRTCVersion = 'libwebrtc-m114.5735.10';
5-
const String iosWebRTCVersion = 'libwebrtc-m114.5735.10';
4+
const String androidWebRTCVersion = 'libwebrtc-m125.6422.03';
5+
const String iosWebRTCVersion = 'libwebrtc-m125.6422.04';

0 commit comments

Comments
 (0)