native_picture_in_picture 1.0.1
native_picture_in_picture: ^1.0.1 copied to clipboard
A Flutter plugin for native Picture-in-Picture (PiP) on Android and iOS. Uses ExoPlayer and AVKit for smooth, reliable PiP with full lifecycle events and auto-PiP support.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:native_picture_in_picture/native_picture_in_picture.dart';
import 'package:native_picture_in_picture/pip_event.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _pip = NativePictureInPicture();
bool _supported = false;
bool _initialized = false;
bool _pipActive = false;
String _lastEvent = 'none';
String _statusMessage = '';
StreamSubscription<PipEvent>? _eventSub;
/// A sample video URL for testing (Apple's official HLS sample stream).
static const _sampleVideoUrl =
'https://devstreaming-cdn.apple.com/videos/streaming/examples/adv_dv_atmos/main.m3u8';
@override
void initState() {
super.initState();
_checkSupport();
_listenToEvents();
}
Future<void> _checkSupport() async {
try {
final supported = await _pip.isPipSupported();
if (!mounted) return;
setState(() {
_supported = supported;
_statusMessage =
supported ? 'PiP is supported' : 'PiP is NOT supported on this device';
});
} on PlatformException catch (e) {
if (!mounted) return;
setState(() {
_statusMessage = 'Error checking support: ${e.message}';
});
}
}
void _listenToEvents() {
_eventSub = _pip.onPipEvent.listen(
(event) {
if (!mounted) return;
setState(() {
_lastEvent = event.name;
if (event == PipEvent.didStart) {
_pipActive = true;
} else if (event == PipEvent.didStop) {
_pipActive = false;
}
});
},
onError: (error) {
if (!mounted) return;
setState(() {
_statusMessage = 'Event error: $error';
});
},
);
}
Future<void> _initialize() async {
try {
await _pip.initialize(_sampleVideoUrl);
if (!mounted) return;
setState(() {
_initialized = true;
_statusMessage = 'Initialized with sample video';
});
} on PlatformException catch (e) {
if (!mounted) return;
setState(() {
_statusMessage = 'Initialize error: ${e.message}';
});
}
}
Future<void> _startPiP() async {
try {
await _pip.startPiP();
if (!mounted) return;
setState(() {
_statusMessage = 'PiP started';
});
} on PlatformException catch (e) {
if (!mounted) return;
setState(() {
_statusMessage = 'Start PiP error: ${e.message}';
});
}
}
Future<void> _stopPiP() async {
try {
await _pip.stopPiP();
if (!mounted) return;
setState(() {
_statusMessage = 'PiP stopped';
});
} on PlatformException catch (e) {
if (!mounted) return;
setState(() {
_statusMessage = 'Stop PiP error: ${e.message}';
});
}
}
Future<void> _disposePiP() async {
try {
await _pip.dispose();
if (!mounted) return;
setState(() {
_initialized = false;
_pipActive = false;
_statusMessage = 'Disposed';
});
} on PlatformException catch (e) {
if (!mounted) return;
setState(() {
_statusMessage = 'Dispose error: ${e.message}';
});
}
}
@override
void dispose() {
_eventSub?.cancel();
_pip.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Native PiP Example')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Status card
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Status',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text('Supported: $_supported'),
Text('Initialized: $_initialized'),
Text('PiP Active: $_pipActive'),
Text('Last Event: $_lastEvent'),
if (_statusMessage.isNotEmpty) ...[
const SizedBox(height: 8),
Text(
_statusMessage,
style: TextStyle(
color: _statusMessage.contains('error') ||
_statusMessage.contains('Error') ||
_statusMessage.contains('NOT')
? Colors.red
: Colors.green,
),
),
],
],
),
),
),
const SizedBox(height: 16),
// Action buttons
ElevatedButton(
onPressed: _supported && !_initialized ? _initialize : null,
child: const Text('Initialize Player'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _initialized && !_pipActive ? _startPiP : null,
child: const Text('Start PiP'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _pipActive ? _stopPiP : null,
child: const Text('Stop PiP'),
),
const SizedBox(height: 8),
OutlinedButton(
onPressed: _initialized ? _disposePiP : null,
child: const Text('Dispose'),
),
],
),
),
),
);
}
}