Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[video_player] ios picture in picture #6284

Closed
wants to merge 83 commits into from
Closed
Show file tree
Hide file tree
Changes from 74 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
42ffe17
Don't force save the picked/taken image to the gallery
NicolaVerbeeck Nov 17, 2021
08f723d
Merge pull request #2 from flutter/main
ikbendewilliam Jul 13, 2022
e7921c2
Merge branch 'master' into source
jorre127 Jul 13, 2022
d319e04
Merge pull request #3 from icapps/source
jorre127 Jul 13, 2022
b54f049
Merge branch 'flutter:main' into master
vanlooverenkoen Aug 16, 2022
4a34420
Merge branch 'flutter:main' into master
vanlooverenkoen Aug 17, 2022
826b995
#60048 Added ios pip support
vanlooverenkoen Aug 17, 2022
8e15600
Updated pubspec
vanlooverenkoen Aug 17, 2022
78be5cf
Removed development team & updated bundle identifier
vanlooverenkoen Aug 17, 2022
5d91352
Updated the pr feedback
vanlooverenkoen Aug 18, 2022
5f7281a
Dependency overrides
vanlooverenkoen Aug 18, 2022
c38e3d3
Added iOS UI tests for the PiP
vanlooverenkoen Aug 18, 2022
f0e4820
Fixed some dart tests
vanlooverenkoen Aug 18, 2022
567e146
FIxed incorrect import
vanlooverenkoen Aug 18, 2022
77287c1
Fixed a dart test where the to string was updated but not fixed in test
vanlooverenkoen Aug 18, 2022
5ca97ca
Fixed formatting
vanlooverenkoen Aug 18, 2022
6309b2f
Revert "Fixed formatting"
vanlooverenkoen Aug 18, 2022
db3eb7d
Fixed the ios formatting
vanlooverenkoen Aug 18, 2022
3a4d91e
Updated android gradle plugin
vanlooverenkoen Aug 18, 2022
f8a3ed6
Check if the PIPUIView is availabel after starting PiP
vanlooverenkoen Aug 19, 2022
3328e52
Fixed pr comments
vanlooverenkoen Aug 19, 2022
17a580e
Fixed pr comments
vanlooverenkoen Aug 19, 2022
3cbfd50
Added extra tests to check if starting is set.
vanlooverenkoen Aug 19, 2022
a9317da
Update the player layer behaviour.
vanlooverenkoen Aug 19, 2022
6572321
Added support to enable or disable automaticly start pip when the app…
vanlooverenkoen Aug 19, 2022
82912b4
Moved to a oneliner
vanlooverenkoen Aug 19, 2022
e4582ec
Merge branch 'main' into feature/ios-pip
vanlooverenkoen Aug 25, 2022
2e2c31d
Fixed the README comments
vanlooverenkoen Sep 9, 2022
63b5bd8
Updated readme
vanlooverenkoen Sep 9, 2022
5c0da92
Fixed pr comments
vanlooverenkoen Sep 9, 2022
071893d
Update the project file
vanlooverenkoen Sep 9, 2022
fd0e9cd
Moved the api to a rect
vanlooverenkoen Sep 9, 2022
2af7230
Removed unwanted enabledVideo value
vanlooverenkoen Sep 9, 2022
f2ee1a4
Updated the isPictureInPicture supported method
vanlooverenkoen Sep 9, 2022
1047384
Fixed pr comments in the switch case
vanlooverenkoen Sep 9, 2022
f9ec3f5
Removed unwanted info.plist items
vanlooverenkoen Sep 9, 2022
5927554
Fixed test
vanlooverenkoen Sep 9, 2022
620c179
Make sure the tests compile
vanlooverenkoen Sep 9, 2022
de5d58a
Updated the boolean arguments
vanlooverenkoen Sep 9, 2022
5fd2bcb
Updated the UI tests to check if pip is supported or not
vanlooverenkoen Sep 9, 2022
3cc7213
Fixed a typo
vanlooverenkoen Sep 9, 2022
ff67016
Fixed typo & made it clear what the switch does
vanlooverenkoen Sep 9, 2022
f34b8b4
Pr feedback
vanlooverenkoen Sep 9, 2022
0c2d632
Added extra documentation
vanlooverenkoen Sep 9, 2022
16b2c0f
Removed the web package
vanlooverenkoen Sep 9, 2022
bab27ed
Fixed pr feedback
vanlooverenkoen Sep 9, 2022
a829a14
Fixed formatting
vanlooverenkoen Sep 9, 2022
85c77e7
Fixed pr comments
vanlooverenkoen Sep 9, 2022
640db39
Merge branch 'flutter:main' into master
vanlooverenkoen Sep 9, 2022
ed675e3
Merge branch 'master' into feature/ios-pip
vanlooverenkoen Sep 9, 2022
7eddf97
Fixed the merge conflicts
vanlooverenkoen Sep 9, 2022
0bc160a
Update ImagePickerDelegate.java
vanlooverenkoen Sep 9, 2022
db8a9b2
Fixed the web pubspec
vanlooverenkoen Sep 9, 2022
699ea6f
Revert "Update ImagePickerDelegate.java"
vanlooverenkoen Sep 9, 2022
2a217d5
Revert "Fixed the web pubspec"
vanlooverenkoen Sep 9, 2022
e621157
Removed link to web
vanlooverenkoen Sep 9, 2022
31b6a9f
Update ImagePickerDelegate.java
vanlooverenkoen Sep 9, 2022
0ba1b21
Trigger build
vanlooverenkoen Sep 9, 2022
c45a2c2
Merge branch 'feature/ios-pip' of github.com:icapps/plugins into feat…
vanlooverenkoen Sep 9, 2022
f188b5f
Fixed formatting
vanlooverenkoen Sep 9, 2022
892aac0
Merge branch 'flutter:main' into master
vanlooverenkoen Sep 26, 2022
c841ad4
Merge branch 'master' into feature/ios-pip
vanlooverenkoen Sep 26, 2022
36333f6
Refactorred some of the api's and fixed pr comments.
vanlooverenkoen Sep 26, 2022
d07cf9c
Cleanup pip to picture in picture
vanlooverenkoen Sep 26, 2022
abe022b
Fixed tests
vanlooverenkoen Sep 26, 2022
66b5ff1
Merge branch 'flutter:main' into master
vanlooverenkoen Oct 5, 2022
467b85e
Fixed merge conflicts
vanlooverenkoen Oct 5, 2022
e9bdfd5
Fixed a comment about the api start/stop
vanlooverenkoen Oct 5, 2022
faed7b8
Fixed comment about the pipUI view test that should be hidden
vanlooverenkoen Oct 5, 2022
3fb924b
Fixed check if buttons are hidden.
vanlooverenkoen Oct 5, 2022
90f993a
Fixed pr comments
vanlooverenkoen Oct 5, 2022
a901313
Merge branch 'main' into feature/ios-pip
vanlooverenkoen Jan 4, 2023
83edcef
Fixed comments
vanlooverenkoen Jan 5, 2023
2b8b565
Updated the gif
vanlooverenkoen Jan 5, 2023
c6f81b7
Fixed pr comments
vanlooverenkoen Jan 18, 2023
6f66373
Fixed formatting
vanlooverenkoen Jan 18, 2023
cc639b2
Fixed compile issue
vanlooverenkoen Jan 18, 2023
23defa3
Merge branch 'main' into feature/ios-pip
vanlooverenkoen Jan 28, 2023
eaaaac2
Merge branch 'flutter:main' into master
vanlooverenkoen Feb 10, 2023
de162e8
Merge branch 'master' into feature/ios-pip
vanlooverenkoen Feb 10, 2023
326c187
Updated the video_player_platfom_interface
vanlooverenkoen Feb 10, 2023
96c788a
Test if this works? It doesn't work on my machine
vanlooverenkoen Feb 10, 2023
565012b
Fixed pr build
vanlooverenkoen Feb 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions packages/video_player/video_player/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,29 @@ and so on.

To learn about playback speed limitations, see the [`setPlaybackSpeed` method documentation](https://pub.dev/documentation/video_player/latest/video_player/VideoPlayerController/setPlaybackSpeed.html).

### Picture in Picture

#### iOS
On iOS the picture in picture is linked to the AVPlayerController.
If you want to enable picture in picture make sure to enable the `audio` capability. (in Xcode it will also say, `audo, airplay & picture in picture`)
Not setting this capability but calling `setPictureInPictureOverlayRectMessage` and `setPictureInPicture` will not start the picture in picture.

```xml
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
```

Example:
![The example app running in iOS with picture in picture enabled](https://github.com/flutter/plugins/blob/main/packages/video_player/video_player/doc/demo_pip_iphone.gif?raw=true)

#### Android

On Android the implementation is different. There is no link to the video player. Your complete app will be minimized ([picture in picutre Android documentation](https://developer.android.com/guide/topics/ui/picture-in-picture))

You have multiple options on Android:
- [simple_pip_mode](https://pub.dev/packages/simple_pip_mode)
- Create your own plugin that follows the andorid documentation

Furthermore, see the example app for an example playback speed implementation.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand Down
85 changes: 83 additions & 2 deletions packages/video_player/video_player/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ class _BumbleBeeRemoteVideo extends StatefulWidget {
class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> {
late VideoPlayerController _controller;

final GlobalKey<State<StatefulWidget>> _playerKey =
GlobalKey<State<StatefulWidget>>();
final Key _pictureInPictureKey = UniqueKey();
bool _enableStartPictureInPictureAutomaticallyFromInline = false;

Future<ClosedCaptionFile> _loadCaptions() async {
final String fileContents = await DefaultAssetBundle.of(context)
.loadString('assets/bumble_bee_captions.vtt');
Expand Down Expand Up @@ -243,17 +248,93 @@ class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> {
children: <Widget>[
Container(padding: const EdgeInsets.only(top: 20.0)),
const Text('With remote mp4'),
FutureBuilder<bool>(
key: _pictureInPictureKey,
future: _controller.isPictureInPictureSupported(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) =>
Text(snapshot.data ?? false
? 'Picture in picture is supported'
: 'Picture in picture is not supported'),
),
Row(
children: <Widget>[
const SizedBox(width: 16),
const Expanded(
child: Text(
'Start picture in picture automatically when going to background'),
),
Switch(
value: _enableStartPictureInPictureAutomaticallyFromInline,
onChanged: (bool newValue) {
setState(() {
_enableStartPictureInPictureAutomaticallyFromInline =
newValue;
});
_controller.setAutomaticallyStartPictureInPicture(
enableStartPictureInPictureAutomaticallyFromInline:
_enableStartPictureInPictureAutomaticallyFromInline);
},
),
const SizedBox(width: 16),
],
),
MaterialButton(
color: Colors.blue,
onPressed: () {
final RenderBox? box =
_playerKey.currentContext?.findRenderObject() as RenderBox?;
if (box == null) {
return;
}
final Offset offset = box.localToGlobal(Offset.zero);
_controller.setPictureInPictureOverlayRect(
rect: Rect.fromLTWH(
offset.dx,
offset.dy,
box.size.width,
box.size.height,
),
);
},
child: const Text('Set picture in picture overlay rect'),
),
MaterialButton(
color: Colors.blue,
onPressed: () {
if (_controller.value.isPictureInPictureActive) {
_controller.stopPictureInPicture();
} else {
_controller.startPictureInPicture();
}
},
child: Text(_controller.value.isPictureInPictureActive
? 'Stop picture in picture'
: 'Start picture in picture'),
),
Container(
padding: const EdgeInsets.all(20),
child: AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: Stack(
key: _playerKey,
alignment: Alignment.bottomCenter,
children: <Widget>[
VideoPlayer(_controller),
ClosedCaption(text: _controller.value.caption.text),
_ControlsOverlay(controller: _controller),
VideoProgressIndicator(_controller, allowScrubbing: true),
if (_controller.value.isPictureInPictureActive) ...<Widget>[
Container(color: Colors.white),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Icon(Icons.picture_in_picture),
SizedBox(height: 8),
Text('This video is playing in picture in picture.'),
],
),
] else ...<Widget>[
VideoProgressIndicator(_controller, allowScrubbing: true),
_ControlsOverlay(controller: _controller),
],
],
),
),
Expand Down
6 changes: 6 additions & 0 deletions packages/video_player/video_player/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,9 @@ flutter:
- assets/bumble_bee_captions.srt
- assets/bumble_bee_captions.vtt
- assets/Audio.mp3


# FOR TESTING ONLY. DO NOT MERGE.
dependency_overrides:
video_player:
path: ../../../video_player/video_player
67 changes: 66 additions & 1 deletion packages/video_player/video_player/lib/video_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class VideoPlayerValue {
this.isPlaying = false,
this.isLooping = false,
this.isBuffering = false,
this.isPictureInPictureActive = false,
this.volume = 1.0,
this.playbackSpeed = 1.0,
this.rotationCorrection = 0,
Expand Down Expand Up @@ -105,6 +106,9 @@ class VideoPlayerValue {
/// The current speed of the playback.
final double playbackSpeed;

/// True if picture in picture is currently active.
final bool isPictureInPictureActive;

/// A description of the error if present.
///
/// If [hasError] is false this is `null`.
Expand Down Expand Up @@ -153,6 +157,7 @@ class VideoPlayerValue {
bool? isPlaying,
bool? isLooping,
bool? isBuffering,
bool? isPictureInPictureActive,
double? volume,
double? playbackSpeed,
int? rotationCorrection,
Expand All @@ -169,6 +174,8 @@ class VideoPlayerValue {
isPlaying: isPlaying ?? this.isPlaying,
isLooping: isLooping ?? this.isLooping,
isBuffering: isBuffering ?? this.isBuffering,
isPictureInPictureActive:
isPictureInPictureActive ?? this.isPictureInPictureActive,
volume: volume ?? this.volume,
playbackSpeed: playbackSpeed ?? this.playbackSpeed,
rotationCorrection: rotationCorrection ?? this.rotationCorrection,
Expand All @@ -191,6 +198,7 @@ class VideoPlayerValue {
'isPlaying: $isPlaying, '
'isLooping: $isLooping, '
'isBuffering: $isBuffering, '
'isPictureInPictureActive: $isPictureInPictureActive, '
'volume: $volume, '
'playbackSpeed: $playbackSpeed, '
'errorDescription: $errorDescription)';
Expand Down Expand Up @@ -399,6 +407,12 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
case VideoEventType.bufferingEnd:
value = value.copyWith(isBuffering: false);
break;
case VideoEventType.startingPictureInPicture:
value = value.copyWith(isPictureInPictureActive: true);
break;
case VideoEventType.stoppedPictureInPicture:
value = value.copyWith(isPictureInPictureActive: false);
break;
case VideoEventType.unknown:
break;
}
Expand Down Expand Up @@ -518,6 +532,53 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
await _videoPlayerPlatform.setVolume(_textureId, value.volume);
}

/// Returns true if picture in picture is supported on the device.
Future<bool> isPictureInPictureSupported() =>
_videoPlayerPlatform.isPictureInPictureSupported();

/// Enable/disable to start picture in picture automatically when the app goes to the background.
Future<void> setAutomaticallyStartPictureInPicture({
required bool enableStartPictureInPictureAutomaticallyFromInline,
}) async {
if (!value.isInitialized || _isDisposed) {
return;
}
await _videoPlayerPlatform.setAutomaticallyStartPictureInPicture(
textureId: _textureId,
enableStartPictureInPictureAutomaticallyFromInline:
enableStartPictureInPictureAutomaticallyFromInline,
);
}

/// Set the location of the video player view. So picture in picture can use it for animating
Future<void> setPictureInPictureOverlayRect({
required Rect rect,
}) async {
if (!value.isInitialized || _isDisposed) {
return;
}
await _videoPlayerPlatform.setPictureInPictureOverlayRect(
textureId: _textureId,
rect: rect,
);
}

/// Start picture in picture mode
Future<void> startPictureInPicture() async {
if (!value.isInitialized || _isDisposed) {
return;
}
await _videoPlayerPlatform.startPictureInPicture(_textureId);
}

/// Stop picture in picture mode
Future<void> stopPictureInPicture() async {
if (!value.isInitialized || _isDisposed) {
return;
}
await _videoPlayerPlatform.stopPictureInPicture(_textureId);
}

Future<void> _applyPlaybackSpeed() async {
if (_isDisposedOrNotInitialized) {
return;
Expand Down Expand Up @@ -689,6 +750,7 @@ class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver {
_VideoAppLifeCycleObserver(this._controller);

bool _wasPlayingBeforePause = false;
bool _isPictureInPictureActive = false;
final VideoPlayerController _controller;

void initialize() {
Expand All @@ -700,7 +762,10 @@ class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver {
switch (state) {
case AppLifecycleState.paused:
_wasPlayingBeforePause = _controller.value.isPlaying;
_controller.pause();
_isPictureInPictureActive = _controller.value.isPictureInPictureActive;
if (!_isPictureInPictureActive) {
_controller.pause();
}
break;
case AppLifecycleState.resumed:
if (_wasPlayingBeforePause) {
Expand Down
14 changes: 13 additions & 1 deletion packages/video_player/video_player/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter
widgets on Android, iOS, and web.
repository: https://github.com/flutter/plugins/tree/main/packages/video_player/video_player
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
version: 2.4.10
version: 2.5.0

environment:
sdk: ">=2.14.0 <3.0.0"
Expand Down Expand Up @@ -31,3 +31,15 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter


# FOR TESTING ONLY. DO NOT MERGE.
dependency_overrides:
video_player_android:
path: ../../video_player/video_player_android
video_player_avfoundation:
path: ../../video_player/video_player_avfoundation
video_player_platform_interface:
path: ../../video_player/video_player_platform_interface
video_player_web:
path: ../../video_player/video_player_web
20 changes: 20 additions & 0 deletions packages/video_player/video_player/test/video_player_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ class FakeController extends ValueNotifier<VideoPlayerValue>
Future<void> setClosedCaptionFile(
Future<ClosedCaptionFile>? closedCaptionFile,
) async {}

@override
Future<bool> isPictureInPictureSupported() async => true;

@override
Future<void> setAutomaticallyStartPictureInPicture({
required bool enableStartPictureInPictureAutomaticallyFromInline,
}) async {}

@override
Future<void> setPictureInPictureOverlayRect({
required Rect rect,
}) async {}

@override
Future<void> startPictureInPicture() async {}

@override
Future<void> stopPictureInPicture() async {}
}

Future<ClosedCaptionFile> _loadClosedCaption() async =>
Expand Down Expand Up @@ -933,6 +952,7 @@ void main() {
'isPlaying: true, '
'isLooping: true, '
'isBuffering: true, '
'isPictureInPictureActive: false, '
'volume: 0.5, '
'playbackSpeed: 1.5, '
'errorDescription: null)');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ class MiniController extends ValueNotifier<VideoPlayerValue> {
case VideoEventType.bufferingEnd:
value = value.copyWith(isBuffering: false);
break;
case VideoEventType.startingPictureInPicture:
case VideoEventType.stoppedPictureInPicture:
case VideoEventType.unknown:
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ flutter:
assets:
- assets/flutter-mark-square-64.png
- assets/Butterfly-209.mp4


# FOR TESTING ONLY. DO NOT MERGE.
dependency_overrides:
video_player_android:
path: ../../../video_player/video_player_android
video_player_platform_interface:
path: ../../../video_player/video_player_platform_interface
6 changes: 6 additions & 0 deletions packages/video_player/video_player_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,9 @@ dev_dependencies:
flutter_test:
sdk: flutter
pigeon: ^2.0.1


# FOR TESTING ONLY. DO NOT MERGE.
dependency_overrides:
video_player_platform_interface:
path: ../../video_player/video_player_platform_interface
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand Down
Loading