zego_pip 0.0.3

SDKflutter
Platformandroidios

A Flutter Picture-in-Picture (PIP) plugin based on `zego_express_engine`, supporting iOS and Android platforms .

zego_pip

A Flutter Picture-in-Picture (PIP) plugin based on zego_express_engine, supporting iOS and Android platforms.

English | 中文

Demo

iOS Platform

iOS PIP Demo

Android Platform

Android PIP Demo

Features

  • 🎥 Cross-platform Support: Supports iOS 15.0+ and Android 8.0+
  • 🚀 Ready to Use: Zero configuration, one-click PIP enablement
  • 🔄 Auto Switching: Intelligent PIP mode switching
  • 🎯 Performance Optimized: Built-in video rendering optimization and memory management

Installation

1. Add Dependencies

Add to pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  zego_pip: ^0.0.1
  zego_express_engine: ^3.21.0

2. Install Dependencies

flutter pub get

3. iOS Configuration

Set minimum version in ios/Podfile:

platform :ios, '13.0'

Then run:

cd ios
pod install
cd ..

4. Android Configuration

Ensure minSdkVersion in android/app/build.gradle is at least 21:

android {
    defaultConfig {
        minSdkVersion 21
    }
}

Add android:supportsPictureInPicture="true" to the <activity> tag in android/app/src/main/AndroidManifest.xml:

<manifest>
  <application>
    <activity
      android:name=".MainActivity"
      android:supportsPictureInPicture="true"
      ...
    />
  </application>
</manifest>

Android Permissions

The plugin automatically adds the following necessary permissions to your app:

Permission CategoryPermission NamePurposeMinimum API Level
Network Permissionsandroid.permission.INTERNETNetwork access for video streamingAPI 1
android.permission.ACCESS_NETWORK_STATENetwork state accessAPI 1
android.permission.ACCESS_WIFI_STATEWiFi state accessAPI 1
Audio Permissionsandroid.permission.RECORD_AUDIOAudio recording for audio captureAPI 1
android.permission.MODIFY_AUDIO_SETTINGSAudio settings modificationAPI 1
Camera Permissionsandroid.permission.CAMERACamera access for video captureAPI 1
Foreground Service Permissionsandroid.permission.FOREGROUND_SERVICEForeground service for background PIPAPI 26
android.permission.FOREGROUND_SERVICE_MICROPHONEForeground service microphoneAPI 34
android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTIONForeground service media projectionAPI 34
System Permissionsandroid.permission.SYSTEM_ALERT_WINDOWSystem overlay for PIP window displayAPI 1

Quick Start

1. Initialize

Method 1: zego_pip Internal Engine Creation

import 'package:zego_pip/zego_pip.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await ZegoPIP().init(
    /// zego_pip internally creates the express engine, listens for events, and logs into the room
    expressConfig: ZegoPIPExpressConfig(
      create: ZegoPIPExpressCreateConfig(
        // Please fill in your own app id
        appID: 1234567890,
        // Please fill in your own app sign
        appSign: 'your_app_sign_here',
      ),
      room: ZegoPIPExpressRoomConfig(
        // Please fill in your own room id
        roomID: 'test_room_id',
        // Please fill in your own room login user info
        userID: 'test_user_id',
        userName: 'test_user_name',
      ),
    ),
  );

  runApp(
    const MaterialApp(
      home: Scaffold(
        // Please fill in your own stream id
        body: Center(child: ZegoPIPVideoView(streamID: 'test_stream_id')),
      ),
    ),
  );
}

Method 2: External Engine Creation(Recommended)

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:zego_express_engine/zego_express_engine.dart';
import 'package:zego_pip/zego_pip.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await ZegoExpressManager().init();

  /// Before ZegoPIP.init, you must first create the express engine
  await ZegoPIP().init(
    expressConfig: ZegoPIPExpressConfig(
      event: ZegoExpressManager().pipExpressEvent,
    ),
  );

  /// ZegoPIPVideoView can only be rendered after logging into the room from other parts of the app
  await ZegoExpressManager().loginRoom();

  runApp(
    const MaterialApp(
      home: Scaffold(
        /// Please fill in your own stream id
        body: Center(child: ZegoPIPVideoView(streamID: 'test_stream_id')),
      ),
    ),
  );
}

class ZegoExpressManager {
  factory ZegoExpressManager() {
    return shared;
  }

  static final ZegoExpressManager shared = ZegoExpressManager._internal();
  ZegoExpressManager._internal();

  var pipExpressEvent = ZegoPIPExpressEvent();

  Future<void> init() async {
    ZegoExpressEngine.onEngineStateUpdate = onEngineStateUpdate;
    ZegoExpressEngine.onDebugError = onDebugError;
    ZegoExpressEngine.onRoomStreamUpdate = onRoomStreamUpdate;
    ZegoExpressEngine.onRoomStateChanged = onRoomStateChanged;
    ZegoExpressEngine.onPlayerStateUpdate = onPlayerStateUpdate;
    ZegoExpressEngine.onRemoteCameraStateUpdate = onRemoteCameraStateUpdate;
    ZegoExpressEngine.onRemoteMicStateUpdate = onRemoteMicStateUpdate;

    await ZegoExpressEngine.createEngineWithProfile(
      ZegoEngineProfile(
        1234567890,
        ZegoScenario.Default,
        appSign: 'your app sign',
        /// Please ensure that `enablePlatformView` is set to true
        enablePlatformView: true,
      ),
    );
  }

  Future<bool> loginRoom() async {
    final result = await ZegoExpressEngine.instance.loginRoom(
      /// Please fill in your own room id
      'test_room_id',
      ZegoUser(
        /// Please fill in your own room login user info
        'test_user_id',
        'test_user_name',
      ),
      config: ZegoRoomConfig(0, true, ''),
    );

    return 0 == result.errorCode;
  }

  /// Forward express event to zego_pip
  void onEngineStateUpdate(ZegoEngineState state) {
    pipExpressEvent.onEngineStateUpdate?.call(state);
  }

  void onDebugError(int errorCode, String funcName, String info) {
    pipExpressEvent.onDebugError?.call(errorCode, funcName, info);
  }

  void onRoomStreamUpdate(
    String roomID,
    ZegoUpdateType updateType,
    List<ZegoStream> streamList,
    Map<String, dynamic> extendedData,
  ) {
    pipExpressEvent.onRoomStreamUpdate?.call(
      roomID,
      updateType,
      streamList,
      extendedData,
    );
  }

  void onRoomStateChanged(
    String roomID,
    ZegoRoomStateChangedReason reason,
    int errorCode,
    Map<String, dynamic> extendedData,
  ) {
    pipExpressEvent.onRoomStateChanged?.call(
      roomID,
      reason,
      errorCode,
      extendedData,
    );
  }

  void onPlayerStateUpdate(
    String streamID,
    ZegoPlayerState state,
    int errorCode,
    Map<String, dynamic> extendedData,
  ) {
    pipExpressEvent.onPlayerStateUpdate?.call(
      streamID,
      state,
      errorCode,
      extendedData,
    );
  }

  void onRemoteCameraStateUpdate(String streamID, ZegoRemoteDeviceState state) {
    pipExpressEvent.onRemoteCameraStateUpdate?.call(streamID, state);
  }

  void onRemoteMicStateUpdate(String streamID, ZegoRemoteDeviceState state) {
    pipExpressEvent.onRemoteMicStateUpdate?.call(streamID, state);
  }
}

2. Use Video Component

class VideoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Video Room')),
      body: ZegoPIPVideoView(
        streamID: 'stream_id',
        // PIP functionality automatically enabled
      ),
    );
  }
}

3. Manual PIP Control (Optional)

class PIPController {
  final zegoPIP = ZegoPIP();
  
  // Enable PIP
  Future<void> enablePIP() async {
    await zegoPIP.enable();
  }
  
  // Update PIP source
  Future<void> updatePIPSource(String streamID) async {
    await zegoPIP.updateIOSPIPSource(streamID);
  }
  
  // Stop PIP
  Future<bool> stopPIP() async {
    return await zegoPIP.stopPIP();
  }
  
  // Check if in PIP mode
  Future<bool> isInPIP() async {
    return await zegoPIP.isInPIP();
  }
}

📖 API Documentation

Platform Support

iOS

  • Minimum Version: iOS 13.0
  • PIP Support: iOS 15.0+
  • Features: Native AVPictureInPictureController support

Android

  • Minimum Version: Android 8.0 (API 26)
  • PIP Support: Android 8.0+
  • Features: Native PictureInPicture mode support

FAQ

Q: Why doesn't PIP functionality work?

A: Please check:

  1. Whether the plugin is properly initialized
  2. Whether iOS version supports PIP (iOS 15.0+)
  3. Whether Android version supports PIP (Android 8.0+)
  4. Whether zego_express_engine dependency is added

Q: How to handle PIP mode switching?

A: The plugin automatically handles mode switching, no manual intervention required.

Q: How to customize PIP interface?

  • Android: You can use ZegoPIPVideoView as a normal Widget in your widget tree.
  • iOS: Currently the plugin uses system default PIP interface, customization is not supported yet.

Known Issues

  • When the remote stream camera is not enabled and ZegoPIPVideoView is rendered, the first desktop minimization after the remote stream enables camera will not show PIP window

Contributing

Issues and Pull Requests are welcome!

License

This project is licensed under the MIT License.