A Flutter plugin that adds Picture-in-Picture (PiP) support for the video_player package.
- Enter/exit PiP mode for videos played with video_player
- Check if PiP is supported on the current device
- Monitor PiP state changes via stream
- Toggle PiP mode
- Works on both Android and iOS platforms
- Android: API level 26 (Android 8.0) or higher
- iOS: iOS 14.0 or higher
- Flutter 3.3.0 or higher
- Dart SDK 3.7.2 or higher
Add this to your package's pubspec.yaml file:
dependencies:
video_player_pip: ^0.0.1Ensure your Android app has the proper permissions in AndroidManifest.xml:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>For Android 12 and higher, also add:
<activity
android:name="YOUR_ACTIVITY_NAME"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
... >
</activity>For iOS, you need to add the following to your Info.plist file to enable background audio playback, which is necessary for PiP functionality when the app is in the background:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>This ensures that your application can continue playing audio when it enters Picture-in-Picture mode and the main app UI is no longer in the foreground.
Here's a simple example of how to use the plugin:
import 'package:flutter/material.dart';
import 'package:video_player_pip/index.dart';
class VideoScreen extends StatefulWidget {
@override
_VideoScreenState createState() => _VideoScreenState();
}
class _VideoScreenState extends State<VideoScreen> {
late VideoPlayerController _controller;
bool _isInitialized = false;
@override
void initState() {
super.initState();
// Create a video controller
_controller = VideoPlayerController.network(
'https://example.com/sample-video.mp4',
);
// Initialize
_controller.initialize().then((_) {
setState(() {
_isInitialized = true;
});
_controller.play();
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Video Player with PiP')),
body: Center(
child: _isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: CircularProgressIndicator(),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
// Calculate dimensions based on aspect ratio
final aspectRatio = _controller.value.aspectRatio;
const width = 300;
final height = (width / aspectRatio).round();
// Enter PiP mode
await _controller.enterPipMode(
width: width,
height: height,
);
},
child: Icon(Icons.picture_in_picture),
),
);
}
}bool isPipSupported = await VideoPlayerPip.isPipSupported();VideoPlayerPip.instance.onPipModeChanged.listen((isInPipMode) {
print('Is in PiP mode: $isInPipMode');
});await VideoPlayerPip.instance.togglePipMode(
_controller,
width: 300,
height: 200,
);await VideoPlayerPip.exitPipMode();/// Gets the current buffering state of the video player.
///
/// For Android, it will use a workaround due to a bug
/// affecting the video_player plugin, preventing it from getting the
/// actual buffering state. This currently results in the VideoPlayerController always buffering,
/// thus breaking UI elements.
///
/// For this, the actual buffer position is used to determine if the video is
/// buffering or not. See Issue #912 for more details.
so use videoPlayerController.getIsBuffering()
bool isPipSupported = await VideoPlayerPip.isPipSupported();Check out the example app for a complete implementation.
This project is licensed under the MIT License - see the LICENSE file for details.
Please file issues, bugs, or feature requests in our issue tracker.
Contributions are welcome! Please feel free to submit a Pull Request.