A control panel laid on top of VideoPlayer. User can do play / pause / seekTo / set volume on it. Support closed captions / subtitle. Support fullscreen.
Windows | Android | iOS | Web |
---|---|---|---|
✔️ (Vista+) | ✔️ | ✔️ | ✔️ |
Playback in Windows is supported by plug-in video_player_win. Playback in Android / iOS / Web is supported by flutter official package video_player.
For Windows, please refer to video_player_win. (Important)
For Android / iOS / Web, please refer to video_player.
Add this to your package's pubspec.yaml
file:
dependencies:
video_player_control_panel: ^1.1.0
Or
dependencies:
video_player_control_panel:
git:
url: https://github.com/jakky1/video_player_control_panel.git
ref: master
Play from network source:
var controller = VideoPlayerController.network("https://www.your-web.com/sample.mp4");
controller.initialize().then((value) {
if (controller.value.isInitialized) {
controller.play();
} else {
log("video file load failed");
}
}).catchError((e) {
log("controller.initialize() error occurs: $e");
});
Play from file:
var controller = VideoPlayerController.file(File("E:\\test.mp4"));
Load subtitle:
String content = _getYourSubtitleContent();
var file = SubRipCaptionFile(content); // if is a subrip (.srt) file
var file = WebVTTCaptionFile(content); // if is a WebVTT (.vtt) file
controller.setClosedCaptionFile( Future.value(file) );
If the file is a video, build a display widget to show video frames with a control panel:
Widget build(BuildContext context) {
return JkVideoControlPanel(controller,
showClosedCaptionButton: true,
showFullscreenButton: true,
showVolumeButton: true,
// onPrevClicked: optional. If provided, a [previous] button will shown
onPrevClicked: (nowPlayIndex <= 0) ? null : () {
playPrevVideo();
},
// onNextClicked: optional. If provided, a [next] button will shown
onNextClicked: (nowPlayIndex >= g_playlist.length - 1) ? null : () {
playNextVideo();
},
// onPlayEnded: optional, called when the current media is play to end.
onPlayEnded: () {
playNextVideo();
},
);
}
- Play:
controller.play();
- Pause:
controller.pause();
- Seek:
controller.seekTo( Duration(minute: 10, second:30) );
- set playback speed: (normal speed: 1.0)
controller.setPlaybackSpeed(1.5);
- set volume: (max: 1.0 , mute: 0.0)
controller.setVolume(0.5);
- set looping:
controller.setLooping(true);
- free resource:
controller.dispose();
void onPlaybackEvent() {
final value = controller.value;
// value.isInitialized (bool)
// value.size (Size, video size)
// value.duration (Duration)
// value.isPlaying (bool)
// value.isBuffering (bool)
// value.position (Duration)
}
controller.addListener(onPlaybackEvent);
...
controller.removeListener(onPlaybackEvent); // remember to removeListener()
controller.dispose();
No need to use controller !
final m_playlist = [
"https://www.test.com/test1.mp4",
"https://www.test.com/test2.mp4",
"E:/test_youtube.mp4",
];
@override
Widget build(BuildContext context) {
return JkVideoPlaylistPlayer(
playlist: m_playlist,
isLooping: true,
autoplay: true,
);
}
To enable D-pad navigation to switch focus between buttons, please set property isTV
to true
.
Widget player = JkVideoControlPanel(
controller!,
isTV: true, // <----- here
);
or
Widget player = JkVideoPlaylistPlayer(
playlist: ["a.mp4", "b.mp4"],
isTV: true, // <----- here
);
If your application may run on TV and other non-TV devices, you should check if the device is a TV by yourself:
var deviceInfo = DeviceInfoPlugin();
var androidInfo = await deviceInfo.androidInfo;
bool isTV = androidInfo.systemFeatures.contains('android.software.leanback');
and add package device_info_plus
in pubspec.yaml
:
dependencies:
device_info_plus:
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:video_player_control_panel/video_player_control_panel.dart';
import 'package:video_player/video_player.dart';
import 'package:video_player_win/video_player_win_plugin.dart';
import 'package:sprintf/sprintf.dart';
void main() {
runApp(const MyApp());
}
String generateCaptionFileContent() {
final sb = StringBuffer();
for (int i=1; i<60*20; i++) {
int minute = i ~/ 60;
int second = i % 60;
sb.writeln("$i");
sb.writeln(sprintf("00:%02d:%02d,000 --> 00:%02d:%02d,900", [minute, second, minute, second]));
sb.writeln("this is caption $i");
sb.writeln("2nd line");
sb.writeln("");
}
return sb.toString();
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late VideoPlayerController controller;
@override
void initState() {
super.initState();
//controller = VideoPlayerController.file(File("E:\\test_youtube.mp4"));
controller = VideoPlayerController.network("https://www.sample-videos.com/video123/mp4/720/big_buck_bunny_720p_30mb.mp4");
var captionFile = Future.value(SubRipCaptionFile(generateCaptionFileContent()));
controller.setClosedCaptionFile(captionFile);
controller.initialize().then((value) {
if (!kIsWeb) controller.play();
setState(() {});
});
}
@override
void dispose() {
super.dispose();
controller.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: JkVideoControlPanel(controller,
showClosedCaptionButton: true,
showFullscreenButton: true,
showVolumeButton: true,
bgColor: Colors.black,
),
),
);
}
}