瀏覽代碼

Merge pull request #20 from CaiJingLong/dev

ijk status widget
Caijinglong 6 年之前
父節點
當前提交
dfac549966

+ 20 - 0
README-EN.md

@@ -30,6 +30,7 @@ Before using library, you can star and download the code to try the example.
       - [get media info](#get-media-info)
       - [screen shot](#screen-shot)
       - [Observer for resource](#observer-for-resource)
+      - [IjkStatus](#ijkstatus)
       - [release resource](#release-resource)
     - [Use self controller UI](#use-self-controller-ui)
     - [Use Texture widget](#use-texture-widget)
@@ -247,8 +248,27 @@ Stream<VideoInfo> videoInfoStream = controller.videoInfoStream;
 
 // Volume change, which should be noted here, refers to the volume change of the current media, not the volume change of the system.
 Stream<bool> volumeStream = controller.playingStream;
+
+// When the state changes, the stream is called.
+// Detailed descriptions of specific states can be seen in the table below.
+Stream<IjkStatus> ijkStatusStream = controller.ijkStatusStream;
+
 ```
 
+#### IjkStatus
+
+| name              | describe                                                       |
+| ----------------- | -------------------------------------------------------------- |
+| noDatasource      | The initial state, or the state after calling the reset method |
+| preparing         | After setting up src, get ready before src.                    |
+| setDatasourceFail | After setting datasource failed.                               |
+| prepared          | The datasource was prepared.                                   |
+| pause             | Media pause.                                                   |
+| error             | An error occurred in playback.                                 |
+| playing           | Media is playing.                                              |
+| complete          | Media is play complete.                                        |
+| disposed          | After Controller calls `dispose()`.                            |
+
 #### release resource
 
 ```dart

+ 36 - 18
README.md

@@ -19,26 +19,27 @@ android 模拟器 mac android sdk 自带的 emulator(API28 android9)可用,其
 ## 目录
 
 - [ijkplayer](#ijkplayer)
-  - [目录](#目录)
+  - [目录](#%E7%9B%AE%E5%BD%95)
   - [English Readme](#english-readme)
-  - [安装](#安装)
-  - [原生部分说明](#原生部分说明)
+  - [安装](#%E5%AE%89%E8%A3%85)
+  - [原生部分说明](#%E5%8E%9F%E7%94%9F%E9%83%A8%E5%88%86%E8%AF%B4%E6%98%8E)
     - [iOS](#ios)
     - [Android](#android)
-  - [入门示例](#入门示例)
-  - [使用](#使用)
-    - [设置](#设置)
-    - [关于销毁](#关于销毁)
-    - [控制器的使用](#控制器的使用)
-      - [设置资源](#设置资源)
-      - [播放器的控制](#播放器的控制)
-      - [获取播放信息](#获取播放信息)
-      - [截取视频帧](#截取视频帧)
-      - [资源监听](#资源监听)
-      - [释放资源](#释放资源)
-    - [自定义控制器 UI](#自定义控制器-ui)
-    - [自定义纹理界面](#自定义纹理界面)
-  - [进度](#进度)
+  - [入门示例](#%E5%85%A5%E9%97%A8%E7%A4%BA%E4%BE%8B)
+  - [使用](#%E4%BD%BF%E7%94%A8)
+    - [设置](#%E8%AE%BE%E7%BD%AE)
+    - [关于销毁](#%E5%85%B3%E4%BA%8E%E9%94%80%E6%AF%81)
+    - [控制器的使用](#%E6%8E%A7%E5%88%B6%E5%99%A8%E7%9A%84%E4%BD%BF%E7%94%A8)
+      - [设置资源](#%E8%AE%BE%E7%BD%AE%E8%B5%84%E6%BA%90)
+      - [播放器的控制](#%E6%92%AD%E6%94%BE%E5%99%A8%E7%9A%84%E6%8E%A7%E5%88%B6)
+      - [获取播放信息](#%E8%8E%B7%E5%8F%96%E6%92%AD%E6%94%BE%E4%BF%A1%E6%81%AF)
+      - [截取视频帧](#%E6%88%AA%E5%8F%96%E8%A7%86%E9%A2%91%E5%B8%A7)
+      - [资源监听](#%E8%B5%84%E6%BA%90%E7%9B%91%E5%90%AC)
+      - [IjkStatus 说明](#ijkstatus-%E8%AF%B4%E6%98%8E)
+      - [释放资源](#%E9%87%8A%E6%94%BE%E8%B5%84%E6%BA%90)
+    - [自定义控制器 UI](#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8E%A7%E5%88%B6%E5%99%A8-ui)
+    - [自定义纹理界面](#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BA%B9%E7%90%86%E7%95%8C%E9%9D%A2)
+  - [进度](#%E8%BF%9B%E5%BA%A6)
   - [LICENSE](#license)
 
 ## English Readme
@@ -273,14 +274,31 @@ Stream<VideoInfo> videoInfoStream = controller.videoInfoStream;
 
 // 音量的变化,这里需要注意,这个变化指的是当前媒体的音量变化,而不是系统的音量变化
 Stream<bool> volumeStream = controller.playingStream;
+
+// 当前Controller状态的监听,取值范围可以查看
+Stream<IjkStatus> ijkStatusStream = controller.ijkStatusStream;
 ```
 
+#### IjkStatus 说明
+
+| 名称              | 说明                     |
+| ----------------- | ------------------------ |
+| noDatasource      | 初始状态/调用`reset()`后 |
+| preparing         | 设置资源中               |
+| setDatasourceFail | 设置资源失败             |
+| prepared          | 准备好播放               |
+| pause             | 暂停                     |
+| error             | 发生错误                 |
+| playing           | 播放中                   |
+| complete          | 播放完毕后               |
+| disposed          | 调用 dispose 后的状态    |
+
 #### 释放资源
 
 ```dart
 await controller.reset(); // 这个方法调用后,会释放所有原生资源,但重新设置dataSource依然可用
 
-await controller.dispose(); //这个方法调用后,当前控制器理论上不再可用,重新设置dataSource无效,且可能会抛出异常
+await controller.dispose(); //这个方法调用后,当前控制器理论上不再可用,重新设置dataSource无效,且可能会抛出异常,确定销毁这个controller时再调用
 ```
 
 ### 自定义控制器 UI

+ 0 - 2
example/lib/page/in_overlay_page.dart

@@ -1,5 +1,3 @@
-import 'dart:ui' as ui;
-
 import 'package:flutter/material.dart';
 import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
 import 'package:ijkplayer_example/i18n/i18n.dart';

+ 4 - 2
lib/flutter_ijkplayer.dart

@@ -1,6 +1,8 @@
 export 'src/error.dart';
 export 'src/ijkplayer.dart';
-export 'package:flutter_ijkplayer/src/entity/video_info.dart';
+export 'src/entity/video_info.dart';
 export 'src/widget/controller_widget_builder.dart'
     show DefaultIJKControllerWidget, VolumeType;
-export 'package:flutter_ijkplayer/src/helper/config.dart';
+export 'src/helper/config.dart';
+export 'src/widget/ijk_status_widget.dart'
+    show IjkStatusWidget, StatusWidgetBuilder;

+ 37 - 7
lib/src/controller.dart → lib/src/controller/controller.dart

@@ -1,4 +1,4 @@
-part of './ijkplayer.dart';
+part of '../ijkplayer.dart';
 
 /// Media Controller
 class IjkMediaController with IjkMediaControllerMixin {
@@ -53,6 +53,9 @@ class IjkMediaController with IjkMediaControllerMixin {
   set isPlaying(bool value) {
     this._isPlaying = value;
     _playingController?.add(value);
+    if (value == true) {
+      _ijkStatus = IjkStatus.playing;
+    }
   }
 
   /// playing state stream controller
@@ -105,6 +108,26 @@ class IjkMediaController with IjkMediaControllerMixin {
   Stream<IjkMediaController> get playFinishStream =>
       _playFinishController.stream;
 
+  IjkStatus __ijkStatus = IjkStatus.noDatasource;
+
+  IjkStatus get ijkStatus => __ijkStatus;
+
+  set _ijkStatus(IjkStatus status) {
+    if (status != __ijkStatus) {
+      __ijkStatus = status;
+      _ijkStatusController?.add(status);
+    } else {
+      __ijkStatus = status;
+    }
+  }
+
+  /// playFinish
+  StreamController<IjkStatus> _ijkStatusController =
+      StreamController.broadcast();
+
+  /// On play finish
+  Stream<IjkStatus> get ijkStatusStream => _ijkStatusController.stream;
+
   /// create ijk texture id from native
   Future<void> _initIjk() async {
     try {
@@ -123,17 +146,20 @@ class IjkMediaController with IjkMediaControllerMixin {
   /// [reset] and close all controller
   void dispose() async {
     await reset();
+    _ijkStatus = IjkStatus.disposed;
     _playingController?.close();
     _videoInfoController?.close();
     _textureIdController?.close();
     _volumeController?.close();
     _playFinishController?.close();
+    _ijkStatusController?.close();
 
     _playingController = null;
     _videoInfoController = null;
     _textureIdController = null;
     _volumeController = null;
     _playFinishController = null;
+    _ijkStatusController = null;
 
     IjkMediaPlayerManager().remove(this);
   }
@@ -146,6 +172,7 @@ class IjkMediaController with IjkMediaControllerMixin {
     _plugin = null;
     eventChannel?.dispose();
     eventChannel = null;
+    _ijkStatus = IjkStatus.noDatasource;
   }
 
   /// set net DataSource
@@ -154,11 +181,13 @@ class IjkMediaController with IjkMediaControllerMixin {
     Map<String, String> headers = const {},
     bool autoPlay = false,
   }) async {
+    _ijkStatus = IjkStatus.preparing;
     await _initDataSource(() async {
       await _plugin?.setNetworkDataSource(
         uri: url,
         headers: headers,
       );
+      _ijkStatus = IjkStatus.prepared;
     }, autoPlay);
   }
 
@@ -168,8 +197,10 @@ class IjkMediaController with IjkMediaControllerMixin {
     String package,
     bool autoPlay = false,
   }) async {
+    _ijkStatus = IjkStatus.preparing;
     await _initDataSource(() async {
       await _plugin?.setAssetDataSource(name, package);
+      _ijkStatus = IjkStatus.prepared;
     }, autoPlay);
   }
 
@@ -208,8 +239,10 @@ class IjkMediaController with IjkMediaControllerMixin {
     File file, {
     bool autoPlay = false,
   }) async {
+    _ijkStatus = IjkStatus.preparing;
     await _initDataSource(() async {
       await _plugin?.setFileDataSource(file.absolute.path);
+      _ijkStatus = IjkStatus.prepared;
     }, autoPlay);
   }
 
@@ -252,6 +285,7 @@ class IjkMediaController with IjkMediaControllerMixin {
     LogUtils.info("$this play");
     await _plugin?.play();
     refreshVideoInfo();
+    _ijkStatus = IjkStatus.playing;
   }
 
   /// pause media
@@ -259,6 +293,7 @@ class IjkMediaController with IjkMediaControllerMixin {
     LogUtils.info("$this pause");
     await _plugin?.pause();
     refreshVideoInfo();
+    _ijkStatus = IjkStatus.pause;
   }
 
   /// seek to second
@@ -346,6 +381,7 @@ class IjkMediaController with IjkMediaControllerMixin {
     isPlaying = videoInfo.isPlaying;
     refreshVideoInfo();
     _playFinishController?.add(this);
+    _ijkStatus = IjkStatus.complete;
   }
 
   /// Intercept the video frame image and get the `Uint8List` format.
@@ -493,9 +529,3 @@ class DataSource {
     return ds;
   }
 }
-
-enum DataSourceType {
-  network,
-  file,
-  asset,
-}

+ 20 - 0
lib/src/controller/enums.dart

@@ -0,0 +1,20 @@
+part of '../ijkplayer.dart';
+
+enum DataSourceType {
+  network,
+  file,
+  asset,
+}
+
+/// Current IjkMedia status
+enum IjkStatus {
+  noDatasource,
+  preparing,
+  setDatasourceFail,
+  prepared,
+  pause,
+  error,
+  playing,
+  complete,
+  disposed,
+}

+ 1 - 1
lib/src/ijk_event_channel.dart → lib/src/controller/ijk_event_channel.dart

@@ -1,4 +1,4 @@
-part of 'ijkplayer.dart';
+part of '../ijkplayer.dart';
 
 class _IJKEventChannel {
   int get textureId => controller?.textureId;

+ 0 - 0
lib/src/ijkplayer_controller_mixin.dart → lib/src/controller/ijkplayer_controller_mixin.dart


+ 22 - 6
lib/src/ijkplayer.dart

@@ -4,19 +4,23 @@ import 'dart:typed_data';
 
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
-import 'package:flutter_ijkplayer/src/ijkplayer_controller_mixin.dart';
 
+import 'controller/ijkplayer_controller_mixin.dart';
+import 'entity/video_info.dart';
+import 'engine/ijk_controller_manager.dart';
 import 'error.dart';
-import 'package:flutter_ijkplayer/src/helper/logutil.dart';
-import 'package:flutter_ijkplayer/src/entity/video_info.dart';
+import 'helper/logutil.dart';
 import 'widget/controller_widget_builder.dart';
 import 'widget/ijkplayer_builder.dart';
-import 'engine/ijk_controller_manager.dart';
+import 'widget/ijk_status_widget.dart';
 
-part 'controller.dart';
-part 'ijk_event_channel.dart';
+part 'controller/controller.dart';
+part 'controller/enums.dart';
+part 'controller/ijk_event_channel.dart';
 part 'engine/manager.dart';
 
+typedef Widget IjkStateWidgetBuilder(IjkMediaController controller);
+
 /// Main Classes of Library
 class IjkPlayer extends StatefulWidget {
   final IjkMediaController mediaController;
@@ -27,12 +31,15 @@ class IjkPlayer extends StatefulWidget {
   /// See [buildDefaultIjkPlayer]
   final IJKTextureBuilder textureBuilder;
 
+  final IjkStateWidgetBuilder stateWidgetBuilder;
+
   /// Main Classes of Library
   const IjkPlayer({
     Key key,
     @required this.mediaController,
     this.controllerWidgetBuilder = defaultBuildIjkControllerWidget,
     this.textureBuilder = buildDefaultIjkPlayer,
+    this.stateWidgetBuilder = IjkStatusWidget.defaultBuildStateWidget,
   }) : super(key: key);
 
   @override
@@ -44,6 +51,9 @@ class IjkPlayerState extends State<IjkPlayer> {
   /// see [IjkMediaController]
   IjkMediaController controller;
   GlobalKey _wrapperKey = GlobalKey();
+
+  IjkStateWidgetBuilder get _ijkStateBuilder => widget.stateWidgetBuilder;
+
   @override
   void initState() {
     super.initState();
@@ -79,10 +89,12 @@ class IjkPlayerState extends State<IjkPlayer> {
       },
     );
     var controllerWidget = widget.controllerWidgetBuilder?.call(controller);
+    var statusWidget = buildIjkStateWidget();
     Widget stack = Stack(
       children: <Widget>[
         IgnorePointer(child: video),
         controllerWidget,
+        statusWidget,
       ],
     );
 //    return stack;
@@ -119,6 +131,10 @@ class IjkPlayerState extends State<IjkPlayer> {
       ),
     );
   }
+
+  Widget buildIjkStateWidget() {
+    return _ijkStateBuilder?.call(controller) ?? Container();
+  }
 }
 
 class _IjkPlayerWrapper extends StatefulWidget {

+ 116 - 0
lib/src/widget/ijk_status_widget.dart

@@ -0,0 +1,116 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
+
+/// Construct a Widget based on the current status.
+typedef Widget StatusWidgetBuilder(
+  BuildContext context,
+  IjkMediaController controller,
+  IjkStatus status,
+);
+
+/// Default IjkStatusWidget
+class IjkStatusWidget extends StatelessWidget {
+  final IjkMediaController controller;
+  final StatusWidgetBuilder statusWidgetBuilder;
+
+  const IjkStatusWidget({
+    this.controller,
+    this.statusWidgetBuilder = IjkStatusWidget.buildStatusWidget,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return StreamBuilder<IjkStatus>(
+      initialData: controller.ijkStatus,
+      stream: controller.ijkStatusStream,
+      builder: (BuildContext context, snapshot) {
+        return buildStatusWidget(context, controller, snapshot.data);
+      },
+    );
+  }
+
+  static Widget defaultBuildStateWidget(IjkMediaController controller) {
+    return IjkStatusWidget(
+      controller: controller,
+    );
+  }
+
+  static Widget buildStatusWidget(
+    BuildContext context,
+    IjkMediaController controller,
+    IjkStatus status,
+  ) {
+    if (status == IjkStatus.noDatasource) {
+      return _buildNothing(context);
+    }
+
+    if (status == IjkStatus.preparing) {
+      return _buildProgressWidget(context);
+    }
+    if (status == IjkStatus.error) {
+      return _buildFailWidget(context);
+    }
+    if (status == IjkStatus.pause) {
+      return _buildCenterIconButton(Icons.play_arrow, controller.play);
+    }
+    if (status == IjkStatus.complete) {
+      return _buildCenterIconButton(Icons.replay, () async {
+        await controller?.seekTo(0);
+        await controller?.play();
+      });
+    }
+    return Container();
+  }
+}
+
+Widget _buildNothing(BuildContext context) {
+  return Center(
+    child: Text(
+      "",
+      style: TextStyle(color: Colors.white),
+    ),
+  );
+}
+
+Widget _buildCenterIconButton(IconData iconData, Function onTap) {
+  return Center(
+    child: Container(
+      width: 50,
+      height: 50,
+      decoration: BoxDecoration(
+        color: Colors.white.withOpacity(0.75),
+        borderRadius: BorderRadius.circular(30),
+      ),
+      child: IconButton(
+        iconSize: 30,
+        color: Colors.black,
+        icon: Icon(iconData),
+        onPressed: onTap,
+      ),
+    ),
+  );
+}
+
+Widget _buildProgressWidget(BuildContext context) {
+  return Center(
+    child: Container(
+      width: 60,
+      height: 60,
+      child: RefreshProgressIndicator(
+        backgroundColor: Colors.transparent,
+        valueColor: AlwaysStoppedAnimation(Colors.white),
+      ),
+    ),
+  );
+}
+
+Widget _buildFailWidget(BuildContext context) {
+  return Center(
+    child: Icon(
+      Icons.error,
+      color: Colors.white,
+      size: 44,
+    ),
+  );
+}