native_video_player_plugin 2.0.5
native_video_player_plugin: ^2.0.5 copied to clipboard
Plugin for implementing video player.
example/lib/main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:native_video_player_plugin/native_video_player_plugin.dart';
void main() => runApp(MaterialApp(home: VideoPage()));
class VideoPage extends StatefulWidget {
const VideoPage({super.key});
@override
State<VideoPage> createState() => _VideoPageState();
}
class _VideoPageState extends State<VideoPage> {
final controller = NativeVideoPlayerController();
// String status = "Status: idle";
bool isLooping = false;
String url =
// "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8";
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4?authkey=JWT AIzaSy";
double width = 320;
double height = 180;
double aspectRatio = 16 / 9;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Native Player")),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(8.0),
child: PlayerStatusText(controller: controller),
),
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.red, width: 2),
color: Colors.black26,
),
width: width,
height: height,
child: CustomVideoPlayerOverlay(
controller: controller,
url: url,
isLoop: isLooping,
),
),
SizedBox(height: 16),
// StatusWidget(controller: controller),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () => controller.load(
url: url,
// , true
),
child: Text("Load"),
),
SizedBox(width: 8),
ElevatedButton(
onPressed: controller.play,
child: Text("Play"),
),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: 8),
ElevatedButton(
onPressed: controller.pause,
child: Text("Pause"),
),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () async {
final fileName = await showDialog<String>(
context: context,
builder: (context) {
final controller = TextEditingController();
return AlertDialog(
title: Text('Имя файла'),
content: TextField(
controller: controller,
decoration: InputDecoration(
hintText: 'screenshot.png',
),
),
actions: [
TextButton(
onPressed: () =>
Navigator.of(context).pop(),
child: Text('Отмена'),
),
TextButton(
onPressed: () => Navigator.of(
context,
).pop(controller.text.trim()),
child: Text('OK'),
),
],
);
},
);
if (fileName == null) return;
final result = await controller
.screenshotAndSaveToGallery(
fileName: fileName.isEmpty ? null : fileName,
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
result['success'] == true
? 'Скриншот сохранён: ${result['message']}'
: 'Ошибка: ${result['message']}',
),
),
);
},
child: Text("Скриншот"),
),
SizedBox(width: 8),
ElevatedButton(
onPressed: () async {
final position = await controller.getPosition();
debugPrint(position.toString());
},
child: Text("Get Position"),
),
SizedBox(width: 8),
ElevatedButton(
onPressed: () async {
final duration = await controller.getDuration();
debugPrint(duration.toString());
},
child: Text("Get Duration"),
),
],
),
],
),
],
),
),
),
);
}
}
class PlayerStatusWidget extends StatefulWidget {
final NativeVideoPlayerController controller;
const PlayerStatusWidget({super.key, required this.controller});
@override
State<PlayerStatusWidget> createState() => _PlayerStatusWidgetState();
}
class _PlayerStatusWidgetState extends State<PlayerStatusWidget> {
PlayerStatus status = PlayerStatus.unknown;
@override
initState() {
super.initState();
widget.controller.addStatusListener(updateStatus);
}
void updateStatus(status) {
if (status == this.status) return;
setState(() {
this.status = status;
});
}
@override
void dispose() {
widget.controller.removeStatusListener(updateStatus);
super.dispose();
}
@override
Widget build(BuildContext context) {
bool showProgressIndicator =
(status == PlayerStatus.buffering ||
status == PlayerStatus.idle ||
status == PlayerStatus.unknown);
return Container(
child: (showProgressIndicator)
? CircularProgressIndicator()
: SizedBox.shrink(),
);
}
}
class PlayerStatusText extends StatefulWidget {
final NativeVideoPlayerController controller;
const PlayerStatusText({super.key, required this.controller});
@override
State<PlayerStatusText> createState() => _PlayerStatusTextState();
}
class _PlayerStatusTextState extends State<PlayerStatusText> {
PlayerStatus status = PlayerStatus.unknown;
@override
initState() {
super.initState();
widget.controller.addStatusListener(updateStatus);
}
void updateStatus(status) {
if (status == this.status) return;
setState(() {
this.status = status;
});
}
@override
void dispose() {
widget.controller.removeStatusListener(updateStatus);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.blue, width: 1),
borderRadius: BorderRadius.circular(8),
color: Colors.transparent,
),
child: Text(
"Status: $status",
style: TextStyle(fontSize: 16, color: Colors.black54),
),
);
}
}
class FullscreenVideoPage extends StatefulWidget {
final String url;
final int initialPosition;
final bool wasPlaying;
const FullscreenVideoPage({
super.key,
required this.url,
required this.initialPosition,
required this.wasPlaying,
});
@override
State<FullscreenVideoPage> createState() => _FullscreenVideoPageState();
}
class _FullscreenVideoPageState extends State<FullscreenVideoPage> {
late final NativeVideoPlayerController controller;
@override
void initState() {
super.initState();
controller = NativeVideoPlayerController();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Stack(
children: [
Center(
child: NativeVideoPlayer(
controller: controller,
onViewCreated: () async {
try {
await controller.load(url: widget.url, isLoop: true);
await Future.delayed(Duration(milliseconds: 500));
// await controller.play();
} catch (e) {
debugPrint('Error initializing fullscreen video: $e');
}
},
),
),
Positioned(
top: 16,
right: 16,
child: IconButton(
icon: Icon(Icons.close, color: Colors.white),
onPressed: () async {
final position = await controller.getPosition();
final isPlaying = await controller.isPlaying();
Navigator.of(
context,
).pop({'position': position, 'isPlaying': isPlaying});
},
),
),
],
),
),
);
}
}
class CustomVideoPlayerOverlay extends StatefulWidget {
final NativeVideoPlayerController controller;
final String url;
final bool isLoop;
final Map<String, String>? headers;
const CustomVideoPlayerOverlay({
super.key,
required this.controller,
required this.url,
this.isLoop = false,
this.headers,
});
@override
State<CustomVideoPlayerOverlay> createState() =>
_CustomVideoPlayerOverlayState();
}
class _CustomVideoPlayerOverlayState extends State<CustomVideoPlayerOverlay> {
bool _showControls = true;
bool _isPlaying = false;
int _position = 0;
int _duration = 1;
Timer? _hideTimer;
@override
void initState() {
super.initState();
widget.controller.addStatusListener(_onStatusChanged);
_startHideTimer();
}
@override
void dispose() {
widget.controller.removeStatusListener(_onStatusChanged);
_hideTimer?.cancel();
super.dispose();
}
void _onStatusChanged(PlayerStatus status) async {
if (status == PlayerStatus.playing) {
setState(() => _isPlaying = true);
} else {
setState(() => _isPlaying = false);
}
_updatePosition();
}
void _startHideTimer() {
_hideTimer?.cancel();
_hideTimer = Timer(const Duration(seconds: 3), () {
setState(() => _showControls = false);
});
}
void _toggleControls() {
setState(() => _showControls = !_showControls);
if (_showControls) _startHideTimer();
}
void _onPlayPause() async {
if (_isPlaying) {
await widget.controller.pause();
} else {
await widget.controller.play();
}
setState(() => _isPlaying = !_isPlaying);
_startHideTimer();
}
void _onFullscreen() async {
final currentPosition = await widget.controller.getPosition();
final currentStatus = await widget.controller.getStatus();
final result = await Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => FullscreenVideoPage(
url: widget.url,
initialPosition: currentPosition,
wasPlaying: currentStatus == PlayerStatus.playing,
),
),
);
if (result is Map) {
final newPosition = result['position'] as int? ?? 0;
final isPlaying = result['isPlaying'] as bool? ?? false;
if (newPosition > 0) {
// await widget.controller.seekTo(newPosition);
}
if (isPlaying) {
await widget.controller.play();
} else {
await widget.controller.pause();
}
}
}
void _onSeek(double value) async {
// await widget.controller.seekTo(value.toInt());
setState(() => _position = value.toInt());
_startHideTimer();
}
Future<void> _updatePosition() async {
final pos = await widget.controller.getPosition();
final dur = await widget.controller.getDuration();
if (mounted) {
setState(() {
_position = pos;
_duration = dur > 0 ? dur : 1;
});
}
if (_isPlaying) {
Future.delayed(const Duration(milliseconds: 250), _updatePosition);
}
}
String _formatDuration(int millis) {
final seconds = (millis / 1000).truncate();
final minutes = (seconds / 60).truncate();
final remainingSeconds = seconds % 60;
return '${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}';
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _toggleControls,
child: Stack(
children: [
NativeVideoPlayer(
controller: widget.controller,
onViewCreated: () async {
await widget.controller.load(
url: widget.url,
isLoop: widget.isLoop,
);
await _updatePosition();
},
),
if (false)
Positioned.fill(
child: Container(
color: Colors.black.withValues(alpha: 0.4),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(
child: Center(
child: IconButton(
iconSize: 64,
icon: Icon(
_isPlaying ? Icons.pause_circle : Icons.play_circle,
color: Colors.white,
),
onPressed: _onPlayPause,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
Text(
_formatDuration(_position),
style: const TextStyle(color: Colors.white),
),
Expanded(
child: Slider(
value: _position.toDouble().clamp(
0,
_duration.toDouble(),
),
min: 0,
max: _duration.toDouble(),
onChanged: (value) => _onSeek(value),
activeColor: Colors.white,
inactiveColor: Colors.white38,
),
),
Text(
_formatDuration(_duration),
style: const TextStyle(color: Colors.white),
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: const Icon(
Icons.fullscreen,
color: Colors.white,
),
onPressed: _onFullscreen,
),
const SizedBox(width: 8),
],
),
],
),
),
),
],
),
);
}
}