123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- import 'dart:async';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:orientation/orientation.dart';
- import 'package:video_player/video_player.dart'; // 引入官方插件
- class MyVideo extends StatefulWidget {
- MyVideo({
- @required this.url, // 当前需要播放的地址
- @required this.width, // 播放器尺寸(大于等于视频播放区域)
- @required this.height,
- this.title = '', // 视频需要显示的标题
- });
- // 视频地址
- final String url;
- // 视频尺寸比例
- final double width;
- final double height;
- // 视频标题
- final String title;
- @override
- State<MyVideo> createState() {
- return _MyVideoState();
- }
- }
- class _MyVideoState extends State<MyVideo> {
- // 指示video资源是否加载完成,加载完成后会获得总时长和视频长宽比等信息
- bool _videoInit = false;
- // video控件管理器
- VideoPlayerController _controller;
- // 记录video播放进度
- Duration _position = Duration(seconds: 0);
- // 记录播放控件ui是否显示(进度条,播放按钮,全屏按钮等等)
- Timer _timer; // 计时器,用于延迟隐藏控件ui
- bool _hidePlayControl = true; // 控制是否隐藏控件ui
- double _playControlOpacity = 0; // 通过透明度动画显示/隐藏控件ui
- // 记录是否全屏
- bool get _isFullScreen =>
- MediaQuery.of(context).orientation == Orientation.landscape;
- @override
- Widget build(BuildContext context) {
- return Container(
- width: widget.width,
- height: widget.height,
- color: Colors.black,
- child: widget.url != null
- ? Stack(
- // 因为控件ui和视频是重叠的,所以要用定位了
- children: <Widget>[
- GestureDetector(
- // 手势组件
- onTap: () {
- // 点击显示/隐藏控件ui
- _togglePlayControl();
- },
- child: _videoInit
- ? Center(
- child: AspectRatio(
- // 加载url成功时,根据视频比例渲染播放器
- aspectRatio: _controller.value.aspectRatio,
- child: VideoPlayer(_controller),
- ),
- )
- : Center(
- // 没加载完成时显示转圈圈loading
- child: SizedBox(
- width: 20,
- height: 20,
- child: CircularProgressIndicator(),
- ),
- ),
- ),
- Positioned(
- // 需要定位
- left: 0,
- bottom: 0,
- child: Offstage(
- // 控制是否隐藏
- offstage: _hidePlayControl,
- child: AnimatedOpacity(
- // 加入透明度动画
- opacity: _playControlOpacity,
- duration: Duration(milliseconds: 300),
- child: Container(
- // 底部控件的容器
- width: widget.width,
- height: 40,
- decoration: BoxDecoration(
- gradient: LinearGradient(
- // 来点黑色到透明的渐变优雅一下
- begin: Alignment.bottomCenter,
- end: Alignment.topCenter,
- colors: [
- Color.fromRGBO(0, 0, 0, .7),
- Color.fromRGBO(0, 0, 0, .1)
- ],
- ),
- ),
- child: _videoInit
- ? Row(
- // 加载完成时才渲染,flex布局
- children: <Widget>[
- IconButton(
- // 播放按钮
- padding: EdgeInsets.zero,
- iconSize: 26,
- icon: Icon(
- // 根据控制器动态变化播放图标还是暂停
- _controller.value.isPlaying
- ? Icons.pause
- : Icons.play_arrow,
- color: Colors.white,
- ),
- onPressed: () {
- setState(() {
- // 同样的,点击动态播放或者暂停
- _controller.value.isPlaying
- ? _controller.pause()
- : _controller.play();
- _startPlayControlTimer(); // 操作控件后,重置延迟隐藏控件的timer
- });
- },
- ),
- Flexible(
- // 相当于前端的flex: 1
- child: VideoProgressIndicator(
- // 嘻嘻,这是video_player编写好的进度条,直接用就是了~~
- _controller,
- allowScrubbing: true, // 允许手势操作进度条
- padding: EdgeInsets.all(0),
- colors: VideoProgressColors(
- // 配置进度条颜色,也是video_player现成的,直接用
- playedColor: Theme.of(context)
- .primaryColor, // 已播放的颜色
- bufferedColor: Color.fromRGBO(
- 255, 255, 255, .5), // 缓存中的颜色
- backgroundColor: Color.fromRGBO(
- 255, 255, 255, .2), // 为缓存的颜色
- ),
- ),
- ),
- Container(
- // 播放时间
- margin: EdgeInsets.only(left: 10),
- child: Text(
- // durationToTime是通过Duration转成hh:mm:ss的格式,自己实现。
- "A",
- style: TextStyle(color: Colors.white),
- ),
- ),
- IconButton(
- // 全屏/横屏按钮
- padding: EdgeInsets.zero,
- iconSize: 26,
- icon: Icon(
- // 根据当前屏幕方向切换图标
- _isFullScreen
- ? Icons.fullscreen_exit
- : Icons.fullscreen,
- color: Colors.white,
- ),
- onPressed: () {
- // 点击切换是否全屏
- _toggleFullScreen();
- },
- ),
- ],
- )
- : Container(),
- ),
- ),
- ),
- )
- ],
- )
- : Center(
- // 判断是否传入了url,没有的话显示"暂无视频信息"
- child: Text(
- '暂无视频信息',
- style: TextStyle(color: Colors.white),
- ),
- ),
- );
- }
- @override
- void initState() {
- _urlChange(); // 初始进行一次url加载
- super.initState();
- }
- @override
- void didUpdateWidget(MyVideo oldWidget) {
- if (oldWidget.url != widget.url) {
- _urlChange(); // url变化时重新执行一次url加载
- }
- super.didUpdateWidget(oldWidget);
- }
- @override
- void dispose() {
- if (_controller != null) {
- // 惯例。组件销毁时清理下
- _controller.removeListener(_videoListener);
- _controller.dispose();
- }
- super.dispose();
- }
- void _urlChange() {
- if (widget.url == null || widget.url == '') return;
- if (_controller != null) {
- // 如果控制器存在,清理掉重新创建
- _controller.removeListener(_videoListener);
- _controller.dispose();
- }
- setState(() {
- // 重置组件参数
- _hidePlayControl = true;
- _videoInit = false;
- _position = Duration(seconds: 0);
- });
- // 加载network的url,也支持本地文件,自行阅览官方api
- _controller = VideoPlayerController.network(widget.url)
- ..initialize().then((_) {
- // 加载资源完成时,监听播放进度,并且标记_videoInit=true加载完成
- _controller.addListener(_videoListener);
- setState(() {
- _videoInit = true;
- });
- });
- }
- void _videoListener() async {
- Duration res = await _controller.position;
- if (res >= _controller.value.duration) {
- _controller.pause();
- _controller.seekTo(Duration(seconds: 0));
- }
- setState(() {
- _position = res;
- });
- }
- void _togglePlayControl() {
- setState(() {
- if (_hidePlayControl) {
- // 如果隐藏则显示
- _hidePlayControl = false;
- _playControlOpacity = 1;
- _startPlayControlTimer(); // 开始计时器,计时后隐藏
- } else {
- // 如果显示就隐藏
- if (_timer != null) _timer.cancel(); // 有计时器先移除计时器
- _playControlOpacity = 0;
- Future.delayed(Duration(milliseconds: 300)).whenComplete(() {
- _hidePlayControl = true; // 延迟300ms(透明度动画结束)后,隐藏
- });
- }
- });
- }
- void _startPlayControlTimer() {
- // 计时器,用法和前端js的大同小异
- if (_timer != null) _timer.cancel();
- // _hidePlayControl = true;
- _timer = Timer(Duration(seconds: 3), () {
- // 延迟3s后隐藏
- setState(() {
- _playControlOpacity = 0;
- Future.delayed(Duration(milliseconds: 300)).whenComplete(() {
- _hidePlayControl = true;
- });
- });
- });
- }
- void _toggleFullScreen() {
- setState(() {
- if (_isFullScreen) {
- // 如果是全屏就切换竖屏
- OrientationPlugin.forceOrientation(DeviceOrientation.portraitUp);
- } else {
- OrientationPlugin.forceOrientation(DeviceOrientation.landscapeRight);
- }
- _startPlayControlTimer(); // 操作完控件开始计时隐藏
- });
- }
- }
|