فهرست منبع

Merge pull request #56 from CaiJingLong/dev

fix some problem
Caijinglong 6 سال پیش
والد
کامیت
6c28d59544

+ 11 - 0
BUG.md

@@ -0,0 +1,11 @@
+## all
+
+- [ ] 全屏模式可自定义
+
+## iOS
+
+- [x] 全屏模式在 iphone xs max 上未全屏
+
+## android
+
+- [ ] 一些 m3u8 无声音

+ 11 - 0
CHANGELOG.md

@@ -1,3 +1,14 @@
+## 0.2.4
+
+fix bug:
+
+- set datasource and play error.
+- iOS xs max fullscreen problem.
+
+new:
+
+- Customizing the interface affects the full screen.
+
 ## 0.2.3
 
 Fixed the problem that a project without kotlin could not be compiled.

+ 20 - 20
README.md

@@ -19,30 +19,30 @@ 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)
-  - [入门示例](#入门示例)
-  - [使用](#使用)
-    - [设置](#设置)
-    - [关于销毁](#关于销毁)
-    - [控制器的使用](#控制器的使用)
-      - [设置资源](#设置资源)
-      - [播放器的控制](#播放器的控制)
-      - [获取播放信息](#获取播放信息)
-      - [截取视频帧](#截取视频帧)
-      - [资源监听](#资源监听)
-      - [IjkStatus 说明](#ijkstatus-说明)
-      - [自定义 Option](#自定义-option)
+  - [入门示例](#%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)
+      - [自定义 Option](#%E8%87%AA%E5%AE%9A%E4%B9%89-option)
         - [IjkOptionCategory](#ijkoptioncategory)
-      - [释放资源](#释放资源)
-    - [自定义控制器 UI](#自定义控制器-ui)
-    - [自定义纹理界面](#自定义纹理界面)
-    - [根据当前状态构建一个 widget](#根据当前状态构建一个-widget)
-  - [进度](#进度)
+      - [释放资源](#%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)
+    - [根据当前状态构建一个 widget](#%E6%A0%B9%E6%8D%AE%E5%BD%93%E5%89%8D%E7%8A%B6%E6%80%81%E6%9E%84%E5%BB%BA%E4%B8%80%E4%B8%AA-widget)
+  - [进度](#%E8%BF%9B%E5%BA%A6)
   - [LICENSE](#license)
 
 ## English Readme

+ 2 - 2
TODOLIST.md

@@ -11,7 +11,7 @@
   - [x] 停止
   - [x] 释放资源
   - [x] 控制音量
-  - [ ] 监听播放完成
+  - [x] 监听播放完成
   - [x] 控制系统音量
     - [x] 修复 iOS 系统音量与逻辑相反的问题
     - [x] 修复 iOS 系统音量出现系统弹窗的问题
@@ -40,7 +40,7 @@
   - [ ] 悬浮窗中播放
     - [ ] 悬浮窗的 UI 控制器
     - [ ] 自定义 UI
-  - [ ] 视频播放出错时的回调通知
+  - [x] 视频播放出错时的回调通知
 - [x] 默认控制器 UI
   - [x] 进度条
   - [x] 播放/暂停按钮

+ 1 - 6
android/src/main/java/top/kikt/ijkplayer/NotifyChannel.kt

@@ -36,7 +36,7 @@ class NotifyChannel(val registry: PluginRegistry.Registrar, val textureId: Long,
         }
         player.setOnErrorListener { mp, what, extra ->
             channel.invokeMethod("error", what)
-            logi("onError $what")
+            logi("onError $what , extra = $extra")
             false
         }
         player.setOnInfoListener { mp, what, extra ->
@@ -60,15 +60,10 @@ class NotifyChannel(val registry: PluginRegistry.Registrar, val textureId: Long,
             logi("onController message $it, isPlaying = ${player.isPlaying}")
             ""
         }
-
     }
 
     fun dispose() {
         player.resetListeners()
     }
 
-}
-
-class Temp {
-    fun invokeMethod(str: String, any: Any?) {}
 }

+ 1 - 1
example/ios/Podfile.lock

@@ -26,7 +26,7 @@ EXTERNAL SOURCES:
 
 SPEC CHECKSUMS:
   Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a
-  flutter_ijkplayer: e7cd28f617bf87bad4a11aaaa13cfc59f18c6c49
+  flutter_ijkplayer: c3a5d569dde01d23b33bf0cfc685a256a10e56ce
   FlutterIJK: 9e3ccb3d74cca7ebe77db65d30a19b630ea8648e
   photo_manager: d47ddf6cb25cbfa837dc334540eb9a99b208e191
 

+ 5 - 0
example/lib/const/resource.dart

@@ -0,0 +1,5 @@
+/// generate by resouce_generator library, shouldn't edit.
+class R {
+  /// ![preview](file:///Users/cai/Documents/GitHub/flutter_ijkplayer/./example/assets/sample1.mp4)
+  static const String ASSETS_SAMPLE1_MP4 = "assets/sample1.mp4";
+}

+ 6 - 0
example/lib/i18n/cn.dart

@@ -68,4 +68,10 @@ class _I18nZh extends I18n {
 
   @override
   String get customOption => "自定义Option的使用";
+
+  @override
+  String get errorUrl => "错误的url";
+
+  @override
+  String get customFullScreenWidget => "自定义全屏界面";
 }

+ 6 - 0
example/lib/i18n/en.dart

@@ -68,4 +68,10 @@ class _I18nEn extends I18n {
 
   @override
   String get customOption => "Usage of custom IjkPlayer options";
+
+  @override
+  String get errorUrl => "Error Url";
+
+  @override
+  String get customFullScreenWidget => "Custom Full Screen Interface";
 }

+ 4 - 0
example/lib/i18n/i18n.dart

@@ -48,6 +48,10 @@ abstract class I18n {
   String get ijkStatusTitle;
 
   String get customOption;
+
+  String get errorUrl;
+
+  String get customFullScreenWidget;
 }
 
 I18n get currentI18n => I18n(window.locale);

+ 78 - 0
example/lib/page/error_url.dart

@@ -0,0 +1,78 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
+import 'package:ijkplayer_example/i18n/i18n.dart';
+
+class ErrorUrlPage extends StatefulWidget {
+  @override
+  _ErrorUrlPageState createState() => _ErrorUrlPageState();
+}
+
+class _ErrorUrlPageState extends State<ErrorUrlPage> {
+  TextEditingController editingController = TextEditingController();
+  IjkMediaController mediaController = IjkMediaController();
+  StreamSubscription statusSub;
+  StreamSubscription ijkErrorSub;
+  @override
+  void initState() {
+    super.initState();
+
+    editingController.text =
+        "https://js.wshls.acgvideo.com/live-js/922199/live_8747041_1741679.m3u8?wsSecret=1337e20698b1673ac73ea8f35e2d60e8&wsTime=1556966389&trid=5afe0383d7d149dabe0c0327c2e53a75&order=1&sig=no";
+
+    statusSub = mediaController.ijkStatusStream.listen((status) {
+      print("status = $status");
+    });
+
+    ijkErrorSub = mediaController.ijkErrorStream.listen((error) {
+      print("error = $error");
+    });
+  }
+
+  @override
+  void dispose() {
+    statusSub?.cancel();
+    ijkErrorSub?.cancel();
+    editingController.dispose();
+    mediaController.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(currentI18n.networkButton),
+      ),
+      body: ListView(
+        children: <Widget>[
+          Row(
+            children: <Widget>[
+              Expanded(
+                child: TextField(
+                  controller: editingController,
+                ),
+              ),
+              FlatButton(
+                child: Text(currentI18n.play),
+                onPressed: _playInput,
+              ),
+            ],
+          ),
+          Container(
+            height: 400,
+            child: IjkPlayer(
+              mediaController: mediaController,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  void _playInput() async {
+    var text = editingController.text;
+    await mediaController.setNetworkDataSource(text, autoPlay: true);
+  }
+}

+ 54 - 0
example/lib/page/full_screen.dart

@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
+import 'package:ijkplayer_example/const/resource.dart';
 import 'package:ijkplayer_example/i18n/i18n.dart';
 
 class FullScreen extends StatefulWidget {
@@ -182,3 +183,56 @@ class _FullScreen2State extends State<FullScreen2> {
     await IjkManager.unlockOrientation();
   }
 }
+
+class CustomFullControllerPage extends StatefulWidget {
+  @override
+  _CustomFullControllerPageState createState() =>
+      _CustomFullControllerPageState();
+}
+
+class _CustomFullControllerPageState extends State<CustomFullControllerPage> {
+  IjkMediaController controller;
+
+  @override
+  void initState() {
+    super.initState();
+    controller = IjkMediaController();
+    controller.setDataSource(
+      DataSource.asset(R.ASSETS_SAMPLE1_MP4),
+      autoPlay: true,
+    );
+  }
+
+  @override
+  void dispose() {
+    controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(),
+      body: Container(
+        height: 500,
+        child: IjkPlayer(
+          mediaController: controller,
+          controllerWidgetBuilder: (ctl) {
+            return DefaultIJKControllerWidget(
+              controller: ctl,
+              fullscreenControllerWidgetBuilder: _buildFullScrrenCtl,
+            );
+          },
+        ),
+      ),
+    );
+  }
+
+  Widget _buildFullScrrenCtl(IjkMediaController controller) {
+    return DefaultIJKControllerWidget(
+      controller: controller,
+      doubleTapPlay: true,
+      currentFullScreenState: true,
+    );
+  }
+}

+ 5 - 1
example/lib/page/index.dart

@@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
 import 'package:ijkplayer_example/page/paging_page.dart';
 import 'package:ijkplayer_example/page/screen_shot_page.dart';
 import 'package:ijkplayer_example/page/video_list.dart';
-import '../i18n/i18n.dart';
 
+import '../i18n/i18n.dart';
+import 'error_url.dart';
 import 'asset_page.dart';
 import 'controller_stream_use.dart';
 import 'custom_ijk_opt_page.dart';
@@ -41,6 +42,9 @@ class _IndexPageState extends State<IndexPage> {
           buildButton(currentI18n.overlayPageTitle, InOverlayPage()),
           buildButton(currentI18n.ijkStatusTitle, IjkStatusPage()),
           buildButton(currentI18n.customOption, CustomIjkOptionPage()),
+          buildButton(currentI18n.errorUrl, ErrorUrlPage()),
+          buildButton(
+              currentI18n.customFullScreenWidget, CustomFullControllerPage()),
         ],
       ),
     );

+ 1 - 1
example/pubspec.lock

@@ -82,7 +82,7 @@ packages:
       path: ".."
       relative: true
     source: path
-    version: "0.2.3"
+    version: "0.2.4"
   flutter_localizations:
     dependency: "direct main"
     description: flutter

+ 19 - 15
ios/Classes/CoolIjkNotifyChannel.m

@@ -99,20 +99,24 @@
 
 
 - (void)moviePlayBackFinish:(NSNotification *)notification {
-//    int reason = [[[notification userInfo] valueForKey:IJKMPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue];
-//    int type = 2;
-//    switch (reason) {
-//        case IJKMPMovieFinishReasonPlaybackEnded:
-//            type = 0;
-//            break;
-//        case IJKMPMovieFinishReasonUserExited:
-//            type = 1;
-//            break;
-//        case IJKMPMovieFinishReasonPlaybackError:
-//            break;
-//        default:
-//            break;
-//    }
+    int reason = [[[notification userInfo] valueForKey:IJKMPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue];
+    int type = 2;
+    switch (reason) {
+        case IJKMPMovieFinishReasonPlaybackEnded:
+            type = 0;
+            break;
+        case IJKMPMovieFinishReasonUserExited:
+            type = 1;
+            break;
+        case IJKMPMovieFinishReasonPlaybackError:{
+            int errValue = [[[notification userInfo] valueForKey:@"error"] intValue];
+            [channel invokeMethod:@"error" arguments:@(errValue)];
+            return;
+        }
+        default:
+            break;
+    }
+    NSLog(@"type = %d", type);
 //    [channel invokeMethod:@"finish" arguments:@{@"type": @(type)}];
     [channel invokeMethod:@"finish" arguments:[self getInfo]];
 }
@@ -127,4 +131,4 @@
     [channel invokeMethod:@"rotateChanged" arguments:[self getInfo]];
 }
 
-@end
+@end

+ 12 - 1
lib/src/controller/controller.dart

@@ -38,6 +38,7 @@ class IjkMediaController
       await eventChannel.init();
       volume = 100;
     } catch (e) {
+      await reset();
       LogUtils.warning(e);
       LogUtils.warning("初始化失败");
     }
@@ -151,7 +152,11 @@ class IjkMediaController
     }
     await _initIjk();
     Future playFuture = _autoPlay(autoPlay);
-    await setDataSource();
+    try {
+      await setDataSource();
+    } on Exception catch (e) {
+      print("init data error is ${e.toString()}");
+    }
     return playFuture;
   }
 
@@ -278,6 +283,12 @@ class IjkMediaController
     _ijkStatus = IjkStatus.complete;
   }
 
+  void _onError(int errorValueInt) async {
+    _playFinishController?.add(this);
+    _ijkStatus = IjkStatus.error;
+    _ijkErrorController?.add(errorValueInt);
+  }
+
   /// Intercept the video frame image and get the `Uint8List` format.
   ///
   /// Player UI is not included. If you need the effect of the player, use the screenshot of the system.

+ 11 - 0
lib/src/controller/ijk_event_channel.dart

@@ -39,6 +39,12 @@ class _IJKEventChannel {
       case "rotateChanged":
         onRotateChanged(call);
         break;
+      case "error":
+        var info = await controller.getVideoInfo();
+        _onPlayFinish(info);
+        int errorValue = call.arguments;
+        _onPlayError(errorValue);
+        break;
       default:
         return MissingPluginException(
           "$channelName ${call.method} not implement",
@@ -93,6 +99,11 @@ class _IJKEventChannel {
     var info = getInfo(call);
     LogUtils.debug("onRotateChanged , info = $info");
   }
+
+  void _onPlayError(int errorValue) {
+    LogUtils.warning("play error , errorValue : $errorValue");
+    controller._onError(errorValue);
+  }
 }
 
 // enum FinishType {

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

@@ -127,6 +127,70 @@ mixin IjkMediaControllerStreamMixin {
   /// On play finish
   Stream<IjkStatus> get ijkStatusStream => _ijkStatusController.stream;
 
+  /// errorStream
+  StreamController<int> _ijkErrorController = StreamController.broadcast();
+
+  /// # On Error stream
+  ///
+  /// In iOS, this value is zero for a lot of time.
+  ///
+  /// see [bilibili-doc](https://github.com/bilibili/ijkplayer/blob/cced91e3ae3730f5c63f3605b00d25eafcf5b97b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMoviePlayerController.m#L1025-L1041)
+  ///
+  /// ----
+  ///
+  /// In android, see next document.
+  ///
+  /// Code value see [bilibili-doc](https://github.com/bilibili/ijkplayer/blob/cced91e3ae3730f5c63f3605b00d25eafcf5b97b/ijkmedia/ijkplayer/android/ijkplayer_android_def.h#L48-L83)
+  ///
+  /// ## Next is the part code
+  ///
+  /// Generic error codes for the media player framework.  Errors are fatal, the
+  /// playback must abort.
+  ///
+  /// Errors are communicated back to the client using the
+  /// MediaPlayerListener::notify method defined below.
+  ///
+  /// ### In this situation, 'notify' is invoked with the following:
+  ///
+  ///  - 'msg' is set to MEDIA_ERROR.
+  ///  - 'ext1' should be a value from the enum media_error_type.
+  ///  - 'ext2' contains an implementation dependant error code to provide
+  ///          more details. Should default to 0 when not used.
+  ///
+  /// ### The codes are distributed as follow:
+  ///
+  ///  - 0xx: Reserved
+  ///  - 1xx: Android Player errors. Something went wrong inside the MediaPlayer.
+  ///  - 2xx: Media errors (e.g Codec not supported). There is a problem with the
+  ///        media itself.
+  ///  - 3xx: Runtime errors. Some extraordinary condition arose making the playback
+  ///        impossible.
+  ///
+  /// ```c
+  ///
+  /// enum media_error_type {
+  ///    // 0xx
+  ///    MEDIA_ERROR_UNKNOWN = 1,
+  ///    // 1xx
+  ///    MEDIA_ERROR_SERVER_DIED = 100,
+  ///    // 2xx
+  ///    MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200,
+  ///    // 3xx
+  ///
+  ///
+  ///    // -xx
+  ///    MEDIA_ERROR_IO          = -1004,
+  ///    MEDIA_ERROR_MALFORMED   = -1007,
+  ///    MEDIA_ERROR_UNSUPPORTED = -1010,
+  ///    MEDIA_ERROR_TIMED_OUT   = -110,
+  ///
+  ///
+  ///    MEDIA_ERROR_IJK_PLAYER  = -10000,
+  /// };
+  ///
+  /// ```
+  Stream<int> get ijkErrorStream => _ijkErrorController.stream;
+
   void _setVolume(int value);
 
   Future<void> _disposeStream() async {
@@ -137,6 +201,7 @@ mixin IjkMediaControllerStreamMixin {
     _volumeController?.close();
     _playFinishController?.close();
     _ijkStatusController?.close();
+    _ijkErrorController?.close();
 
     _playingController = null;
     _videoInfoController = null;
@@ -144,5 +209,6 @@ mixin IjkMediaControllerStreamMixin {
     _volumeController = null;
     _playFinishController = null;
     _ijkStatusController = null;
+    _ijkErrorController = null;
   }
 }

+ 9 - 3
lib/src/ijkplayer.dart

@@ -5,21 +5,27 @@ import 'dart:typed_data';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
-import 'entity/video_info.dart';
-import 'entity/options.dart';
 import 'engine/ijk_controller_manager.dart';
+import 'entity/options.dart';
+import 'entity/video_info.dart';
 import 'error.dart';
 import 'helper/logutil.dart';
 import 'widget/controller_widget_builder.dart';
-import 'widget/ijkplayer_builder.dart';
 import 'widget/ijk_status_widget.dart';
+import 'widget/ijkplayer_builder.dart';
 
 part 'controller/controller.dart';
+
 part 'controller/datasoure.dart';
+
 part 'controller/enums.dart';
+
 part 'controller/ijk_event_channel.dart';
+
 part 'controller/ijkplayer_controller_mixin.dart';
+
 part 'controller/plugin.dart';
+
 part 'engine/manager.dart';
 
 /// Main Classes of Library

+ 49 - 0
lib/src/route/fullscreen_route.dart

@@ -0,0 +1,49 @@
+import 'package:flutter/material.dart';
+
+typedef Widget AnimationPageBuilder(BuildContext context,
+    Animation<double> animation, Animation<double> secondaryAnimation);
+
+class DialogRoute<T> extends PageRoute<T> {
+  final Color barrierColor;
+  final String barrierLabel;
+  final bool maintainState;
+  final Duration transitionDuration;
+  final AnimationPageBuilder builder;
+
+  DialogRoute({
+    this.barrierColor = const Color(0x44FFFFFF),
+    this.barrierLabel = "full",
+    this.maintainState = true,
+    this.transitionDuration = const Duration(milliseconds: 300),
+    @required this.builder,
+  }) : assert(barrierColor != Colors.transparent,
+            "The barrierColor must not be transparent.");
+
+  @override
+  Widget buildPage(BuildContext context, Animation<double> animation,
+      Animation<double> secondaryAnimation) {
+    return builder(context, animation, secondaryAnimation);
+  }
+}
+
+class FullScreenRoute<T> extends DialogRoute<T> {
+  FullScreenRoute({WidgetBuilder builder})
+      : super(builder: (ctx, a, s) => fullScreenBuilder(ctx, builder, a, s));
+
+  static Widget fullScreenBuilder(
+    BuildContext context,
+    WidgetBuilder builder,
+    Animation<double> animation,
+    Animation<double> secondaryAnimation,
+  ) {
+    return AnimatedBuilder(
+      animation: animation,
+      builder: (BuildContext context, Widget child) {
+        return Opacity(
+          opacity: animation.value,
+          child: builder(context),
+        );
+      },
+    );
+  }
+}

+ 51 - 7
lib/src/widget/controller_widget_builder.dart

@@ -7,6 +7,7 @@ import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
 import 'package:flutter_ijkplayer/src/helper/logutil.dart';
 import 'package:flutter_ijkplayer/src/helper/time_helper.dart';
 import 'package:flutter_ijkplayer/src/helper/ui_helper.dart';
+import 'package:flutter_ijkplayer/src/route/fullscreen_route.dart';
 import 'package:flutter_ijkplayer/src/widget/progress_bar.dart';
 
 /// Using mediaController to Construct a Controller UI
@@ -16,8 +17,8 @@ typedef Widget IJKControllerWidgetBuilder(IjkMediaController controller);
 Widget defaultBuildIjkControllerWidget(IjkMediaController controller) {
   return DefaultIJKControllerWidget(
     controller: controller,
-//    verticalGesture: false,
-//    horizontalGesture: false,
+    fullscreenControllerWidgetBuilder: (ctl) =>
+        buildFullscreenMediaController(ctl),
   );
 }
 
@@ -47,6 +48,8 @@ class DefaultIJKControllerWidget extends StatefulWidget {
   /// The current full-screen button style should not be changed by users.
   final bool currentFullScreenState;
 
+  final IJKControllerWidgetBuilder fullscreenControllerWidgetBuilder;
+
   /// The UI of the controller.
   const DefaultIJKControllerWidget({
     @required this.controller,
@@ -57,12 +60,41 @@ class DefaultIJKControllerWidget extends StatefulWidget {
     this.playWillPauseOther = true,
     this.currentFullScreenState = false,
     this.showFullScreenButton = true,
+    this.fullscreenControllerWidgetBuilder,
     Key key,
   }) : super(key: key);
 
   @override
   _DefaultIJKControllerWidgetState createState() =>
       _DefaultIJKControllerWidgetState();
+
+  DefaultIJKControllerWidget copyWith({
+    IjkMediaController controller,
+    bool doubleTapPlay,
+    bool verticalGesture,
+    bool horizontalGesture,
+    VolumeType volumeType,
+    bool playWillPauseOther,
+    bool currentFullScreenState,
+    bool showFullScreenButton,
+    IJKControllerWidgetBuilder fullscreenControllerWidgetBuilder,
+    Key key,
+  }) {
+    return DefaultIJKControllerWidget(
+      controller: controller ?? this.controller,
+      doubleTapPlay: doubleTapPlay ?? this.doubleTapPlay,
+      fullscreenControllerWidgetBuilder: fullscreenControllerWidgetBuilder ??
+          this.fullscreenControllerWidgetBuilder,
+      horizontalGesture: horizontalGesture ?? this.horizontalGesture,
+      currentFullScreenState:
+          currentFullScreenState ?? this.currentFullScreenState,
+      key: key,
+      volumeType: volumeType ?? this.volumeType,
+      playWillPauseOther: playWillPauseOther ?? this.playWillPauseOther,
+      showFullScreenButton: showFullScreenButton ?? this.showFullScreenButton,
+      verticalGesture: verticalGesture ?? this.verticalGesture,
+    );
+  }
 }
 
 class _DefaultIJKControllerWidgetState extends State<DefaultIJKControllerWidget>
@@ -171,6 +203,11 @@ class _DefaultIJKControllerWidgetState extends State<DefaultIJKControllerWidget>
       return Container();
     }
     var isFull = widget.currentFullScreenState;
+
+    IJKControllerWidgetBuilder fullscreenBuilder =
+        widget.fullscreenControllerWidgetBuilder ??
+            (ctx) => widget.copyWith(currentFullScreenState: true);
+
     return IconButton(
       color: Colors.white,
       icon: Icon(isFull ? Icons.fullscreen_exit : Icons.fullscreen),
@@ -178,7 +215,8 @@ class _DefaultIJKControllerWidgetState extends State<DefaultIJKControllerWidget>
         if (isFull) {
           Navigator.pop(context);
         } else {
-          showFullScreenIJKPlayer(context, controller);
+          showFullScreenIJKPlayer(context, controller,
+              fullscreenControllerWidgetBuilder: fullscreenBuilder);
         }
       },
     );
@@ -634,18 +672,20 @@ enum VolumeType {
 }
 
 showFullScreenIJKPlayer(
-    BuildContext context, IjkMediaController controller) async {
+  BuildContext context,
+  IjkMediaController controller, {
+  IJKControllerWidgetBuilder fullscreenControllerWidgetBuilder,
+}) async {
   Navigator.push(
     context,
-    MaterialPageRoute(
+    FullScreenRoute(
       builder: (c) {
         return IjkPlayer(
           mediaController: controller,
           controllerWidgetBuilder: (ctl) =>
-              _buildFullScreenMediaController(ctl, true),
+              fullscreenControllerWidgetBuilder(ctl),
         );
       },
-      fullscreenDialog: true,
     ),
   ).then((_) {
     IjkManager.unlockOrientation();
@@ -686,3 +726,7 @@ Widget _buildFullScreenMediaController(
     currentFullScreenState: true,
   );
 }
+
+Widget buildFullscreenMediaController(IjkMediaController controller) {
+  return _buildFullScreenMediaController(controller, true);
+}

+ 2 - 2
pubspec.yaml

@@ -1,11 +1,11 @@
 name: flutter_ijkplayer
 description: Flutter version of bilibilibili ijkplayer, supports common playback protocols, easy to use.
-version: 0.2.3
+version: 0.2.4
 author: caijinglong<cjl_spy@163.com>
 homepage: https://github.com/CaiJingLong/flutter_ijkplayer
 
 environment:
-  sdk: ">=2.1.0 <3.0.0"
+  sdk: '>=2.1.0 <3.0.0'
 
 dependencies:
   flutter: