video_player_pip

A Flutter plugin that adds Picture-in-Picture (PiP) support for the video_player package.

Features

  • 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

Requirements

  • 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

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  video_player_pip: ^0.0.1

Platform-specific Setup

Android

Ensure 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>

iOS

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.

Usage

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),
      ),
    );
  }
}

Additional Features

Check if PiP is supported

bool isPipSupported = await VideoPlayerPip.isPipSupported();

Listen to PiP state changes

VideoPlayerPip.instance.onPipModeChanged.listen((isInPipMode) {
  print('Is in PiP mode: $isInPipMode');
});

Toggle PiP mode

await VideoPlayerPip.instance.togglePipMode(
  _controller, 
  width: 300, 
  height: 200, 
);

Exit PiP mode programmatically

await VideoPlayerPip.exitPipMode();

isBuffering

/// 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();

Example

Check out the example app for a complete implementation.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Issues and Feedback

Please file issues, bugs, or feature requests in our issue tracker.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.