一個高自由度的Flutter 視訊播放器

初冬發表於2020-01-15

寫在前面

我們往往寫專案都是著急上手,總想著先寫後面有時間了再(說)來(的)優(好)化(聽),這樣寫出來的程式碼往往都是跟業務系統耦合在一塊要麼就是質量不高,下次使用的時候就想重構,有人說專案急沒辦法(曾經我也這麼說過),但是一個好的程式設計習慣真的可以做到事半功倍。既然是一種習慣,那麼肯定是可以改的,首先我要先習慣利用思維導圖來明確明白自己要什麼,有什麼功能,開始設計介面以及引數,最後才是分優先順序然後根據優先順序來執行下一步該做什麼,這樣可以很大高效的避免踩坑,避免因為設計不合理需要重構就麻煩了。閒話不多說下面進入正題。

那麼什麼叫高度自由的播放器呢?我是這麼認為的:首先儘可能的開放許可權來更改配置,其次模組化設計,同時還需要跟業務程式碼解耦,光有這些還不算夠,最後我們需要讓視訊播放器實現自定義擴充,也就是說現有播放器功能滿足不了自己需求的時候,開發者可以自己動手,例如視訊的字幕彈幕視訊頂部控制欄等。

一個高自由度的Flutter 視訊播放器

一張效果圖

快速通道

高自由度的播放器應該具備哪些功能?

  • 1.所有圖示均可自定義
  • 2.播放器自定義配置
    • 視訊開始播放的起始位置
    • 是否自動播放
    • 是否迴圈播放
    • 視訊比例
    • 進度條是否允許拖拽
    • 視訊快進/快退的單位秒數
  • 3.自定義擴充子元素
    • 字幕(也可以使用內建字幕)
    • 彈幕
    • 其他元素(例如:廣告覆蓋;自定義視訊頂部返回按鈕;快進或快退等操作的漸入漸出的提示等等)
  • 4.自定義播放器樣式
    • 播放按鈕是否顯示(視訊暫停時檢視中央的播放按鈕)
    • 自定義播放按鈕(視訊暫停時檢視中央的播放按鈕)
    • 控制欄自定義配置
      • 自定義順序(通過陣列來控制元素)
      • 自定義元素(通過陣列來控制順序)
      • 自定義背景顏色
      • 自定義圖示(播放、暫停、快進、快退、全屏、取消全屏)
      • 進度條樣式自由配置(背景顏色、快取區顏色、進度條顏色)
      • 音量控制元素(尚未完成)
      • 內建彈幕(尚未完成,尚可自定義)
      • 控制欄擴充(尚未完成)
    • 內建字幕自定義配置(或者通過自定義擴充來自己重寫字幕)
      • 設定主字幕或輔字幕的字型大小
      • 設定主字幕或輔字幕的字型顏色
  • 5.手勢支援
    • 單擊(選單欄顯示或隱藏)
    • 雙擊(暫停或播放)
    • 滑動快進或快退(未完成)
  • 6.橫豎屏切換
  • 7.常亮避免鎖屏

一圖勝千言

一個高自由度的Flutter 視訊播放器

通過上圖我們可以看一下播放器都有哪些屬性呢?

播放器屬性:

屬性 型別 描述
dataSource String 視訊URL或媒體檔案的路徑
children List 自定義擴充的子元素,需要使用 WidgetAlign(字幕、彈幕、視訊頂部控制欄等)
onplay VideoCallback 視訊開始播放的回撥
onpause VideoCallback 視訊暫停播放回撥
ontimeupdate VideoCallback 視訊播放進度回撥(通過返回的value進行字幕匹配)
onend VideoCallback 視訊播放結束回撥
playOptions VideoPlayOptions 視訊播放自定義配置(詳情見下方的Useage
videoStyle VideoStyle 視訊播放器自定義樣式(詳情見下方的Useage

播放器自定義配置 (VideoPlayOptions):

屬性 型別 描述
startPosition Duration 開始播放節點,例如:Duration(seconds: 0))
loop bool 是否迴圈播放
seekSeconds num 設定視訊快進/快退單位秒數
autoplay bool 是否自動播放
aspectRatio num 視訊播放比例,例如:16/9 或者 4/3
allowScrubbing bool 是否執行進度條拖拽

播放器自定義樣式 (VideoStyle):

屬性 型別 描述
playIcon Widget 視訊暫停播放時中央顯示的圖示,showPlayIcon為false時,該屬性設定無效。
showPlayIcon bool 暫停時是否顯示播放按鈕
videoControlBarStyle VideoControlBarStyle 控制欄自定義樣式
videoSubtitlesStyle VideoSubtitles 字幕自定義樣式

控制欄自定義樣式 (VideoControlBarStyle):

屬性 型別 描述
barBackgroundColor Color 控制欄背景顏色,預設為Color.fromRGBO(0, 0, 0, 0.5)
playedColor Color 已播放的進度條顏色(下圖1詳細說明)
bufferedColor Color 已緩衝的進度條顏色(下圖1詳細說明)
backgroundColor Color 進度條背景顏色(下圖1詳細說明)
playIcon Widget 控制欄播放圖示(下圖2詳細說明)
pauseIcon Widget 控制欄暫停圖示(下圖2詳細說明)
rewindIcon Widget 控制欄快退圖示(下圖2詳細說明)
forwardIcon Widget 控制欄快進圖示(下圖2詳細說明)
fullscreenIcon Widget 控制欄全屏圖示(下圖2詳細說明)
fullscreenExitIcon Widget 控制欄取消全屏圖示(下圖2詳細說明)
itemList List 控制欄自定義功能(下圖3詳細說明),預設為["rewind", "play", "forward", "progress", "time", "fullscreen"]。如果我們需要調整控制欄顯示的順序,僅需要調整 list 中字串的順序,如果需要刪減,直接從 list 中移除改字串即可,例如移除快進和快退,則講 list 設定為 ["play", "progress", "time", "fullscreen"] 即可。後面會陸續開放自定義元素,也就是你把你的元素加入到 list 中。

控制欄顏色自定義

圖1

圖示自定義

圖2

控制欄進度條自定義元素

圖3

如何使用?

Install & Set up

  1. 新增依賴,開啟根目錄的pubspec.yaml檔案,在dependencies:下面新增以下程式碼

    awsome_video_player: #latest
    複製程式碼
  2. 安裝依賴(如果已經自動安裝請忽略)

    cd 專案目錄
    flutter packages get
    複製程式碼
  3. 在頁面中引入庫

    import 'package:awsome_video_player/awsome_video_player.dart';
    複製程式碼

Useage

這是一個完整的

main.dart

import 'package:flutter/material.dart';

import 'package:awsome_video_player/awsome_video_player.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  String mainSubtitles = "主字幕";
  String subSubtitles = "輔字幕";

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Awsome video player'),
        ),
        body: Center(
          child: AwsomeVideoPlayer(
            "https://www.runoob.com/try/demo_source/movie.mp4",
            /// 視訊播放配置
            playOptions: VideoPlayOptions(
              seekSeconds: 30,
              aspectRatio: 16 / 9,
              loop: true,
              autoplay: true,
              allowScrubbing: true,
              startPosition: Duration(seconds: 0)),
            /// 自定義視訊樣式              
            videoStyle: VideoStyle(
            	/// 自定義視訊暫停時視訊中部的播放按鈕
              playIcon: Icon(
                Icons.play_circle_outline,
                size: 100,
                color: Colors.white,
              ),
              /// 暫停時是否顯示視訊中部播放按鈕              
              showPlayIcon: true,
              /// 自定義底部控制欄              
              videoControlBarStyle: VideoControlBarStyle(       
                /// 自定義顏色
                //playedColor: Colors.red,
                //bufferedColor: Colors.yellow,
                //backgroundColor: Colors.green,
                //barBackgroundColor: Colors.blue,
                /// 更改進度欄的播放按鈕
                playIcon: Icon(
                  Icons.play_arrow, 
                  color: Colors.white, 
                  size: 16
                ),
                /// 更改進度欄的暫停按鈕
                pauseIcon: Icon(
                  Icons.pause,
                  color: Colors.red,
                  size: 16,
                ),
                /// 更改進度欄的快退按鈕
                rewindIcon: Icon(
                  Icons.replay_30,
                  size: 16,
                  color: Colors.white,
                ),
                /// 更改進度欄的快進按鈕
                forwardIcon: Icon(
                  Icons.forward_30,
                  size: 16,
                  color: Colors.white,
                ),
                /// 更改進度欄的全屏按鈕
                fullscreenIcon: Icon(
                  Icons.fullscreen,
                  size: 16,
                  color: Colors.white,
                ),
                /// 更改進度欄的退出全屏按鈕
                fullscreenExitIcon: Icon(
                  Icons.fullscreen_exit,
                  size: 16,
                  color: Colors.red,
                ),
                /// 決定控制欄的元素以及排序,示例見上方圖3
                itemList: [
                  "rewind",
                  "play",
                  "forward",
                  "progress",
                  "time",
                  "fullscreen"
                ],
              ),
              /// 自定義字幕
              videoSubtitlesStyle: VideoSubtitles(
              	mianTitle: Align(
                  alignment: Alignment.bottomCenter,
                  child: Container(
                    padding: EdgeInsets.fromLTRB(10, 0, 10, 30),
                    child: Text(
                    		mainSubtitles,
                        maxLines: 2,
                        textAlign: TextAlign.center,
                        style: TextStyle(
                            color: Colors.white, fontSize: 14)),
                  ),
                ),
                subTitle: Align(
                  alignment: Alignment.bottomCenter,
                  child: Container(
                    padding: EdgeInsets.all(10),
                    child: Text(
                    		subSubtitles,
                        maxLines: 2,
                        textAlign: TextAlign.center,
                        style: TextStyle(
                            color: Colors.white, fontSize: 14)),
                  ),
                ),
              ),
            ),
            /// 自定義擴充元素
            children: [
            	/// 新增自定義視訊頂部返回按鈕
              Align(
                alignment: Alignment.topLeft,
                child: GestureDetector(
                  onTap: () {
                    print("This is test from children.");
                  },
                  child: Container(
                    margin: EdgeInsets.only(top: 5, left: 5),
                    width: 30,
                    height: 30,
                    decoration: BoxDecoration(
                      color: Color.fromRGBO(0, 0, 0, .5),
                      borderRadius: BorderRadius.all(Radius.circular(15)),
                    ),
                    child: Icon(Icons.arrow_back, size: 16, color: Colors.white,)
                  ),
                ),
              ),

              /// 這個將會覆蓋的視訊內容,因為這個層級是最高階,因此手勢會失效(慎用)
              /// 這個可以用來做視訊廣告
              // Positioned(
              //   top: 0,
              //   left: 0,
              //   bottom: 0,
              //   right: 0,
              //   child: Text("data", style: TextStyle(color: Colors.white),),
              // ),
            ],
            /// 視訊暫停回撥
            onpause: (value) {
              print("video paused");
            },
            /// 視訊播放回撥
            onplay: (value) {
              print("video played");
            },
            /// 視訊播放結束回撥
            onended: (value) {
              print("video ended");
            },
            /// 視訊播放進度回撥
            /// 可以用來匹配字幕
            ontimeupdate: (value) {
              print("timeupdate ${value}");
              var position = value.position.inMilliseconds / 1000;
              //根據 position 來判斷當前顯示的字幕
            },
          ),
        ),
      ),
    );
  }
}
複製程式碼

示例展示

1. 自定義控制欄圖示

首先我來看一下控制欄的圖示自定義(見上圖2):

DEMO: main.dart

import 'package:flutter/material.dart';

import 'package:awsome_video_player/awsome_video_player.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Awsome video player'),
        ),
        body: Center(
          child: AwsomeVideoPlayer(
            "https://www.runoob.com/try/demo_source/movie.mp4",
            /// 視訊播放配置
            playOptions: VideoPlayOptions(
              seekSeconds: 30,
              aspectRatio: 16 / 9,
              loop: true,
              autoplay: true,
              allowScrubbing: true,
              startPosition: Duration(seconds: 0)),
            /// 自定義視訊樣式 - 請注意我要劃重點了
            videoStyle: VideoStyle(
              /// 自定義底部控制欄  - 這是重點了
              videoControlBarStyle: VideoControlBarStyle(       
                /// 更改進度欄的播放按鈕
                playIcon: Icon(
                  Icons.play_arrow, 
                  color: Colors.white, 
                  size: 16
                ),
                /// 更改進度欄的暫停按鈕
                pauseIcon: Icon(
                  Icons.pause,
                  color: Colors.red,
                  size: 16,
                ),
                /// 更改進度欄的快退按鈕
                rewindIcon: Icon(
                  Icons.replay_30,
                  size: 16,
                  color: Colors.white,
                ),
                /// 更改進度欄的快進按鈕
                forwardIcon: Icon(
                  Icons.forward_30,
                  size: 16,
                  color: Colors.white,
                ),
                /// 更改進度欄的全屏按鈕
                fullscreenIcon: Icon(
                  Icons.fullscreen,
                  size: 16,
                  color: Colors.white,
                ),
                /// 更改進度欄的退出全屏按鈕
                fullscreenExitIcon: Icon(
                  Icons.fullscreen_exit,
                  size: 16,
                  color: Colors.red,
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

複製程式碼

2. 自定義控制欄元素和順序

我們可以根據videoStylevideoControlBarStyle來自定義控制欄的樣式:調整順序(見上方圖3)只需要調整 itemList中字串的順序即可;控制顯示的元素,將不需要暫時的元素從 itemList 中刪除即可。

DEMO: main.dart

import 'package:flutter/material.dart';

import 'package:awsome_video_player/awsome_video_player.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Awsome video player'),
        ),
        body: Center(
          child: AwsomeVideoPlayer(
            "https://www.runoob.com/try/demo_source/movie.mp4",
            /// 視訊播放配置
            playOptions: VideoPlayOptions(
              seekSeconds: 30,
              aspectRatio: 16 / 9,
              loop: true,
              autoplay: true,
              allowScrubbing: true,
              startPosition: Duration(seconds: 0)),
            /// 自定義視訊樣式 - 請注意我要劃重點了
            videoStyle: VideoStyle(
              /// 自定義底部控制欄  - 這是重點了
              videoControlBarStyle: VideoControlBarStyle(       
                /// 決定控制欄的元素以及排序,示例見上方圖3
                itemList: [
                  "progress",// 這裡將進度條前置了,因此有了圖3的效果
                  "rewind",//如果需要刪除快退按鈕,將其註釋或刪除即可
                  "play",
                  "forward",
                  "time",
                  "fullscreen"
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}
複製程式碼

3. 自定義進度條以及控制欄的背景顏色

同樣我們還是通過videoStylevideoControlBarStyle來自定義進度條的顏色(見上方圖3)。

DEMO: main.dart

import 'package:flutter/material.dart';

import 'package:awsome_video_player/awsome_video_player.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Awsome video player'),
        ),
        body: Center(
          child: AwsomeVideoPlayer(
            "https://www.runoob.com/try/demo_source/movie.mp4",
            /// 視訊播放配置
            playOptions: VideoPlayOptions(
              seekSeconds: 30,
              aspectRatio: 16 / 9,
              loop: true,
              autoplay: true,
              allowScrubbing: true,
              startPosition: Duration(seconds: 0)),
            /// 自定義視訊樣式              
            videoStyle: VideoStyle(
              /// 自定義底部控制欄              
              videoControlBarStyle: VideoControlBarStyle(       
                /// 自定義顏色
                playedColor: Colors.red,//已播放進度條的顏色
                bufferedColor: Colors.yellow,//已快取進度條的顏色
                backgroundColor: Colors.green,//進度條的背景顏色
                barBackgroundColor: Colors.blue,//控制欄的背景顏色
              ),
            ),
          ),
        ),
      ),
    );
  }
}

複製程式碼

注意事項(Q&A)

  • 視訊如果需要橫豎屏不能使用safeArea

  • 視訊的 dataSoure不能為空,為空時使用載入檢視,否則播放器會報錯

    import 'package:flutter/material.dart';
    
    import 'package:awsome_video_player/awsome_video_player.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
    
      String videoUrl = "https://www.runoob.com/try/demo_source/movie.mp4";
    
      @override
      void initState() {
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Awsome video player'),
            ),
            body: Center(
            	/// 一般videoUrl是從服務端返回,在沒有返回來之前,
            	/// 我們可以使用載入檢視來佔位
              child: videoUrl != "" ? AwsomeVideoPlayer(
                videoUrl,
                /// 視訊播放配置
                playOptions: VideoPlayOptions(
                  seekSeconds: 30,
                  aspectRatio: 16 / 9,
                  loop: true,
                  autoplay: true,
                  allowScrubbing: true,
                  startPosition: Duration(seconds: 0)),
              ) : AspectRatio(
                aspectRatio: 16 / 9,
                child: Center(
                  child: CircularProgressIndicator(strokeWidth: 2.0),
                ),
              ),
            ),
          ),
        );
      }
    }
    
    複製程式碼
  • AwsomeVideoPlayer下面的children僅支援AlignPositioned,children的層級會高於下面,這個功能會持續更新,後面會陸續出一些針對自定義擴充的高階文件。

寫在最後

開發過程中遇到問題,請通過以下方式聯絡我,我會第一時間回覆你:

一個高自由度的Flutter 視訊播放器

我的微信

一個高自由度的Flutter 視訊播放器

掃碼進入技術進階交流群

License

Copyright © 2020, Mark Chen. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

相關文章