Procházet zdrojové kódy

Merge pull request #27 from CaiJingLong/dev

Support use custom option to IjkPlayer.
Update iOS support swift.
Refactory controller code.
Caijinglong před 6 roky
rodič
revize
1d9bf5fbcb

+ 6 - 0
CHANGELOG.md

@@ -1,3 +1,9 @@
+## 0.2.2
+
+Add `setIjkPlayerOptions` and `addIjkPlayerOptions`.
+
+iOS podspec add `s.static_framework = true`
+
 ## 0.2.1
 
 Recompile android so library.

+ 43 - 3
README-EN.md

@@ -2,7 +2,7 @@
 
 [![pub package](https://img.shields.io/pub/v/flutter_ijkplayer.svg)](https://pub.dartlang.org/packages/flutter_ijkplayer)
 
-ijkplayer for bilibili/ijkplayer, use flutter Texture widget.
+ijkplayer for [bilibili/ijkplayer](https://github.com/bilibili/ijkplayer), use flutter Texture widget.
 Read this README and refer to example/lib/main.dart before using it.
 The question of Android might not be able to run will be explained in detail.
 The simulator is out of use, so please use the real machine for debugging.
@@ -31,6 +31,8 @@ Before using library, you can star and download the code to try the example.
       - [screen shot](#screen-shot)
       - [Observer for resource](#observer-for-resource)
       - [IjkStatus](#ijkstatus)
+      - [自定义 Option](#%E8%87%AA%E5%AE%9A%E4%B9%89-option)
+        - [IjkOptionCategory](#ijkoptioncategory)
       - [release resource](#release-resource)
     - [Use self controller UI](#use-self-controller-ui)
     - [Build widget from IjkStatus](#build-widget-from-ijkstatus)
@@ -52,8 +54,7 @@ dependencies:
 
 Current config file see [url](https://gitee.com/kikt/ijkplayer_thrid_party/blob/master/config/module.sh).
 
-Compilation options for ijkplayer:
-https://github.com/CaiJingLong/flutter_ijkplayer_pod/blob/master/config/module.sh
+For custom configuration options, refer to the [bibibili/ijkplayer](https://github.com/bilibili/ijkplayer) or [ffmpeg](http://ffmpeg.org/).
 
 Custom compilation options:
 https://github.com/CaiJingLong/flutter_ijkplayer/blob/master/compile.md
@@ -270,6 +271,45 @@ Stream<IjkStatus> ijkStatusStream = controller.ijkStatusStream;
 | complete          | Media is play complete.                                        |
 | disposed          | After Controller calls `dispose()`.                            |
 
+#### 自定义 Option
+
+Support custom IJKPlayer options, which are transmitted directly to Android/iOS native. For specific values and meanings, you need to see bilibili/ijkplayer](https://github.com/bilibili/ijkplayer).
+
+However, this option does not take effect immediately.
+It will only take effect if you call `setDataSource` again.
+
+Setup method `setIjkPlayerOptions`
+
+```dart
+void initIjkController() async {
+  var option1 = IjkOption(IjkOptionCategory.format, "fflags", "fastseek");// category, key ,value
+
+  controller.setIjkPlayerOptions(
+    [TargetPlatform.iOS, TargetPlatform.android],
+    [option1].toSet(),
+  );
+
+  await controller.setDataSource(
+    DataSource.network(
+        "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4"),
+    autoPlay: true,
+  );
+}
+```
+
+第一个参数是一个数组,代表了你 option 目标设备的类型(android/iOS)
+
+第二个参数是一个`Set<IjkOption>`,代表了 Option 的集合,因为 category 和 key 均相同的情况下会覆盖,所以这里使用了 set
+
+##### IjkOptionCategory
+
+| name   |
+| ------ |
+| format |
+| codec  |
+| sws    |
+| player |
+
 #### release resource
 
 ```dart

+ 43 - 2
README.md

@@ -2,7 +2,7 @@
 
 [![pub package](https://img.shields.io/pub/v/flutter_ijkplayer.svg)](https://pub.dartlang.org/packages/flutter_ijkplayer)
 
-ijkplayer,通过纹理的方式接入 bilibili/ijkplayer
+ijkplayer,通过纹理的方式接入 [bilibili/ijkplayer](https://github.com/bilibili/ijkplayer)
 
 使用前请完整阅读本 README 并参阅 example/lib/main.dart
 
@@ -36,6 +36,8 @@ android 模拟器 mac android sdk 自带的 emulator(API28 android9)可用,其
       - [截取视频帧](#%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)
       - [释放资源](#%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)
@@ -62,7 +64,7 @@ dependencies:
 
 ## 原生部分说明
 
-编译规则可以参考[这个](https://gitee.com/kikt/ijkplayer_thrid_party/blob/master/config/module.sh),如果你有自己的特定需求,可以修改编译选项,这个参考 bilibili/ijkplayer 或 ffmpeg
+编译规则可以参考[这个](https://gitee.com/kikt/ijkplayer_thrid_party/blob/master/config/module.sh),如果你有自己的特定需求,可以修改编译选项,这个参考 [bilibili/ijkplayer](https://github.com/bilibili/ijkplayer)[ffmpeg](http://ffmpeg.org/)
 
 自定义编译选项可以看[这里](https://github.com/CaiJingLong/flutter_ijkplayer/blob/master/compile-cn.md)
 
@@ -294,6 +296,45 @@ Stream<IjkStatus> ijkStatusStream = controller.ijkStatusStream;
 | complete          | 播放完毕后               |
 | disposed          | 调用 dispose 后的状态    |
 
+#### 自定义 Option
+
+支持自定义 IJKPlayer 的 option,这个 option 会直接传输至 android/iOS 原生,具体的数值和含义你需要查看[bilibili/ijkplayer](https://github.com/bilibili/ijkplayer)的设置选项
+
+但这个设置后的选项不是即时生效的
+只有在你重新 setDataSource 以后才会生效
+
+设置方法`setIjkPlayerOptions`
+
+```dart
+void initIjkController() async {
+  var option1 = IjkOption(IjkOptionCategory.format, "fflags", "fastseek");// category, key ,value
+
+  controller.setIjkPlayerOptions(
+    [TargetPlatform.iOS, TargetPlatform.android],
+    [option1].toSet(),
+  );
+
+  await controller.setDataSource(
+    DataSource.network(
+        "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4"),
+    autoPlay: true,
+  );
+}
+```
+
+第一个参数是一个数组,代表了你 option 目标设备的类型(android/iOS)
+
+第二个参数是一个`Set<IjkOption>`,代表了 Option 的集合,因为 category 和 key 均相同的情况下会覆盖,所以这里使用了 set
+
+##### IjkOptionCategory
+
+| name   |
+| ------ |
+| format |
+| codec  |
+| sws    |
+| player |
+
 #### 释放资源
 
 ```dart

+ 7 - 5
TODOLIST.md

@@ -37,9 +37,9 @@
   - [x] 屏幕旋转: 这个指强制当前屏幕旋转至哪个方向
     - [ ] iPad 无效,暂不知原因
   - [x] 截图
-  - [ ]  悬浮窗中播放
-    - [ ] 悬浮窗的UI控制器
-    - [ ] 自定义UI
+  - [ ] 悬浮窗中播放
+    - [ ] 悬浮窗的 UI 控制器
+    - [ ] 自定义 UI
 - [x] 默认控制器 UI
   - [x] 进度条
   - [x] 播放/暂停按钮
@@ -74,6 +74,8 @@
   - [x] 在列表中(ListView)
   - [x] 视频竖向分页滑动
   - [x] 在悬浮窗中播放
-    - [x] 悬浮窗的UI控制器
-      - [ ] 允许自定义UI
+    - [x] 悬浮窗的 UI 控制器
 - [x] iOS 部分视频无法显示图像的问题: 可能很长时间内都无法解决
+- [ ] 支持在 dart 端初始化 ijkPlayer 播放器的 option
+  - [ ] android
+  - [ ] iOS

+ 36 - 1
android/src/main/java/top/kikt/ijkplayer/Ijk.kt

@@ -8,13 +8,14 @@ import android.util.Base64
 import io.flutter.plugin.common.MethodCall
 import io.flutter.plugin.common.MethodChannel
 import io.flutter.plugin.common.PluginRegistry
+import top.kikt.ijkplayer.entity.IjkOption
 import top.kikt.ijkplayer.entity.Info
 import tv.danmaku.ijk.media.player.IjkMediaPlayer
 import tv.danmaku.ijk.media.player.TextureMediaPlayer
 import java.io.ByteArrayOutputStream
 import java.io.File
 
-class Ijk(private val registry: PluginRegistry.Registrar) : MethodChannel.MethodCallHandler {
+class Ijk(private val registry: PluginRegistry.Registrar, val options: Map<String, Any>) : MethodChannel.MethodCallHandler {
 
     private val textureEntry = registry.textures().createSurfaceTexture()
     val id: Long
@@ -50,6 +51,40 @@ class Ijk(private val registry: PluginRegistry.Registrar) : MethodChannel.Method
 
         //        mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", maxCacheSize)
         //        mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", if (isBufferCache) 1 else 0)
+
+        fun setOptionToMediaPlayer(option: IjkOption) {
+            if (option.isInit.not()) {
+                return
+            }
+            val category = when (option.type) {
+                IjkOption.Type.Format -> IjkMediaPlayer.OPT_CATEGORY_FORMAT
+                IjkOption.Type.Player -> IjkMediaPlayer.OPT_CATEGORY_PLAYER
+                IjkOption.Type.Sws -> IjkMediaPlayer.OPT_CATEGORY_SWS
+                IjkOption.Type.Codec -> IjkMediaPlayer.OPT_CATEGORY_CODEC
+                else -> -1
+            }
+
+            if (category == -1) {
+                return
+            }
+
+            val value = option.value
+            when (value) {
+                is Int -> mediaPlayer.setOption(category, option.key, value.toLong())
+                is String -> mediaPlayer.setOption(category, option.key, value)
+                is Long -> mediaPlayer.setOption(category, option.key, value)
+            }
+        }
+
+        val options = options["options"]
+        if (options is List<*>) {
+            for (option in options) {
+                if (option is Map<*, *>) {
+                    val ijkOptions = IjkOption(option)
+                    setOptionToMediaPlayer(ijkOptions)
+                }
+            }
+        }
     }
 
     override fun onMethodCall(call: MethodCall?, result: MethodChannel.Result?) {

+ 2 - 2
android/src/main/java/top/kikt/ijkplayer/IjkManager.kt

@@ -9,8 +9,8 @@ import java.util.*
 class IjkManager(private val registrar: PluginRegistry.Registrar) {
     private val ijkList = ArrayList<Ijk>()
 
-    fun create(): Ijk {
-        val ijk = Ijk(registrar)
+    fun create(options: Map<String, Any>): Ijk {
+        val ijk = Ijk(registrar,options)
         ijkList.add(ijk)
         return ijk
     }

+ 2 - 1
android/src/main/java/top/kikt/ijkplayer/IjkplayerPlugin.kt

@@ -28,7 +28,8 @@ class IjkplayerPlugin(private val registrar: Registrar) : MethodCallHandler {
             }
             "create" -> {
                 try {
-                    val ijk = manager.create()
+                    val options: Map<String, Any> = call.arguments()
+                    val ijk = manager.create(options)
                     result.success(ijk.id)
                 } catch (e: Exception) {
                     e.printStackTrace()

+ 50 - 0
android/src/main/java/top/kikt/ijkplayer/entity/IjkOption.kt

@@ -0,0 +1,50 @@
+package top.kikt.ijkplayer.entity
+
+/// create 2019/4/12 by cai
+
+class IjkOption(option: Map<*, *>) {
+
+    lateinit var type: Type
+    lateinit var key: String
+    lateinit var value: Any
+
+    var isInit = true
+
+    init {
+        val type = option["category"]
+        if (type is Int) {
+            this.type = convertIntToType(type)
+        }
+
+        val key = option["key"]
+        if (key is String) {
+            this.key = key
+            isInit = isInit and true
+        }
+
+        option["value"]?.let {
+            this.value = it
+            isInit = isInit and true
+        }
+
+        isInit = (type is Int) and (key is String) and (option.containsKey("value"))
+    }
+
+    private fun convertIntToType(typeInt: Int): Type {
+        return when (typeInt) {
+            0 -> Type.Format
+            1 -> Type.Codec
+            2 -> Type.Sws
+            3 -> Type.Player
+            else -> Type.Other
+        }
+    }
+
+    enum class Type {
+        Format,
+        Codec,
+        Sws,
+        Player,
+        Other,
+    }
+}

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

@@ -65,4 +65,7 @@ class _I18nZh extends I18n {
 
   @override
   String get ijkStatusTitle => "IjkStatus的使用";
+
+  @override
+  String get customOption => "自定义Option的使用";
 }

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

@@ -35,6 +35,7 @@ class _I18nEn extends I18n {
 
   @override
   String get autoFullScreenTitle => "Rotate device to see change";
+
   @override
   String get changeFullScreenWithButton => "Click button to show change";
 
@@ -64,4 +65,7 @@ class _I18nEn extends I18n {
 
   @override
   String get ijkStatusTitle => "Usage of IjkStatus";
+
+  @override
+  String get customOption => "Usage of custom IjkPlayer options";
 }

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

@@ -46,6 +46,8 @@ abstract class I18n {
   String get overlayPageTitle;
 
   String get ijkStatusTitle;
+
+  String get customOption;
 }
 
 I18n get currentI18n => I18n(window.locale);

+ 58 - 0
example/lib/page/custom_ijk_opt_page.dart

@@ -0,0 +1,58 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
+import 'package:ijkplayer_example/i18n/i18n.dart';
+
+class CustomIjkOptionPage extends StatefulWidget {
+  @override
+  _CustomIjkOptionPageState createState() => _CustomIjkOptionPageState();
+}
+
+class _CustomIjkOptionPageState extends State<CustomIjkOptionPage> {
+  IjkMediaController controller = IjkMediaController();
+
+  @override
+  void initState() {
+    super.initState();
+    initIjkController();
+  }
+
+  @override
+  void dispose() {
+    controller?.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(currentI18n.customOption),
+      ),
+      body: ListView(
+        children: <Widget>[
+          AspectRatio(
+            aspectRatio: 1280 / 720,
+            child: IjkPlayer(
+              mediaController: controller,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  void initIjkController() async {
+    var option1 = IjkOption(IjkOptionCategory.format, "fflags", "fastseek");
+
+    controller.setIjkPlayerOptions(
+      [TargetPlatform.iOS, TargetPlatform.android],
+      [option1].toSet(),
+    );
+
+    await controller.setDataSource(
+      DataSource.network(
+          "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4"),
+      autoPlay: true,
+    );
+  }
+}

+ 12 - 9
example/lib/page/index.dart

@@ -1,16 +1,18 @@
 import 'package:flutter/material.dart';
-import 'package:ijkplayer_example/i18n/i18n.dart';
-import 'package:ijkplayer_example/page/asset_page.dart';
-import 'package:ijkplayer_example/page/controller_stream_use.dart';
-import 'package:ijkplayer_example/page/dialog_video_page.dart';
-import 'package:ijkplayer_example/page/full_screen.dart';
-import 'package:ijkplayer_example/page/gallery_page.dart';
-import 'package:ijkplayer_example/page/ijk_status_page.dart';
-import 'package:ijkplayer_example/page/in_overlay_page.dart';
-import 'package:ijkplayer_example/page/network.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 'asset_page.dart';
+import 'controller_stream_use.dart';
+import 'custom_ijk_opt_page.dart';
+import 'dialog_video_page.dart';
+import 'full_screen.dart';
+import 'gallery_page.dart';
+import 'ijk_status_page.dart';
+import 'in_overlay_page.dart';
+import 'network.dart';
 
 class IndexPage extends StatefulWidget {
   @override
@@ -38,6 +40,7 @@ class _IndexPageState extends State<IndexPage> {
           buildButton(currentI18n.screenshotTitle, ScreenShotPage()),
           buildButton(currentI18n.overlayPageTitle, InOverlayPage()),
           buildButton(currentI18n.ijkStatusTitle, IjkStatusPage()),
+          buildButton(currentI18n.customOption, CustomIjkOptionPage()),
         ],
       ),
     );

+ 1 - 1
example/pubspec.lock

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

+ 4 - 1
ios/Classes/CoolFlutterIJK.h

@@ -5,11 +5,14 @@
 #import <Foundation/Foundation.h>
 #import <Flutter/Flutter.h>
 #import "CoolIjkNotifyChannel.h"
+#import "CoolIjkOption.h"
 
 @interface CoolFlutterIJK : NSObject
 
 @property(nonatomic, strong) NSObject <FlutterPluginRegistrar> *registrar;
 
+@property(nonatomic, strong) NSArray<CoolIjkOption*> *options;
+
 - (instancetype)initWithRegistrar:(NSObject <FlutterPluginRegistrar> *)registrar;
 
 + (instancetype)ijkWithRegistrar:(NSObject <FlutterPluginRegistrar> *)registrar;
@@ -20,4 +23,4 @@
 
 - (void)setDegree:(int)degree;
 
-@end
+@end

+ 45 - 0
ios/Classes/CoolFlutterIJK.m

@@ -173,6 +173,51 @@
 //    [options setPlayerOptionIntValue:0      forKey:@"video-max-frame-width-default"];
 //    [options setPlayerOptionIntValue:1      forKey:@"videotoolbox"];
 
+    for (CoolIjkOption *opt in self.options) {
+        if (opt) {
+            NSString* key = opt.key;
+            BOOL isString = [opt.value isKindOfClass:[NSString class]];
+            BOOL isInt;
+            if([opt.value isKindOfClass:[NSNumber class]]){
+                isInt = strcmp([opt.value objCType], @encode(int)) == 0;
+            }else{
+                isInt = NO;
+            }
+            switch (opt.type) {
+                case 0:
+                    if(isString){
+                        [options setFormatOptionValue:opt.value forKey:key];
+                    }else if(isInt){
+                        [options setFormatOptionIntValue:[opt.value intValue] forKey:key];
+                    }
+                    break;
+                case 1:
+                    if(isString){
+                        [options setCodecOptionValue:opt.value forKey:key];
+                    }else if(isInt){
+                        [options setCodecOptionIntValue:[opt.value intValue] forKey:key];
+                    }
+                    break;
+                case 2:
+                    if(isString){
+                        [options setSwsOptionValue:opt.value forKey:key];
+                    }else if(isInt){
+                        [options setSwsOptionIntValue:[opt.value intValue] forKey:key];
+                    }
+                    break;
+                case 3:
+                    if(isString){
+                        [options setPlayerOptionValue:opt.value forKey:key];
+                    }else if(isInt){
+                        [options setPlayerOptionIntValue:[opt.value intValue] forKey:key];
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+    
     return options;
 }
 

+ 1 - 1
ios/Classes/CoolFlutterIjkManager.h

@@ -15,7 +15,7 @@
 
 + (instancetype)managerWithRegistrar:(NSObject <FlutterPluginRegistrar> *)registrar;
 
-- (int64_t)create;
+- (int64_t)createWithCall:(FlutterMethodCall*) call;
 
 - (CoolFlutterIJK *)findIJKWithId:(int64_t)id1;
 

+ 25 - 1
ios/Classes/CoolFlutterIjkManager.m

@@ -5,6 +5,7 @@
 #import "CoolFlutterIjkManager.h"
 #import "CoolFlutterIJK.h"
 #import <IJKMediaFramework/IJKMediaFramework.h>
+#import "CoolIjkOption.h"
 
 @implementation CoolFlutterIjkManager {
     NSMutableDictionary<NSNumber *, CoolFlutterIJK *> *dict;
@@ -25,13 +26,36 @@
     return [[self alloc] initWithRegistrar:registrar];
 }
 
-- (int64_t)create {
+- (int64_t)createWithCall:(FlutterMethodCall*) call {
+    NSArray<CoolIjkOption*>* options = [self getOptionsFromCall:call];
     CoolFlutterIJK *ijk = [CoolFlutterIJK ijkWithRegistrar:self.registrar];
+    ijk.options = options;
     NSNumber *number = @([ijk id]);
     dict[number] = ijk;
     return [ijk id];
 }
 
+-(NSArray<CoolIjkOption*>*)getOptionsFromCall:(FlutterMethodCall *)call{
+    NSMutableArray<CoolIjkOption*> *array = [NSMutableArray new];
+    
+    NSDictionary *args = call.arguments;
+    NSArray *dartOptions = args[@"options"];
+    
+    for (NSDictionary *dict in dartOptions){
+        int type = [dict[@"category"] intValue];
+        NSString *key = dict[@"key"];
+        id value = dict[@"value"];
+        CoolIjkOption *option = [CoolIjkOption new];
+        option.key = key;
+        option.value = value;
+        option.type = type;
+        
+        [array addObject:option];
+    }
+    
+    return array;
+}
+
 - (CoolFlutterIJK *)findIJKWithId:(int64_t)id {
     return dict[@(id)];
 }

+ 20 - 0
ios/Classes/CoolIjkOption.h

@@ -0,0 +1,20 @@
+//
+//  CoolIjkOption.h
+//  flutter_ijkplayer
+//
+//  Created by Caijinglong on 2019/4/12.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface CoolIjkOption : NSObject
+
+@property(nonatomic,assign) int type;
+@property(nonatomic,copy) NSString *key;
+@property(nonatomic,copy) id value;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 12 - 0
ios/Classes/CoolIjkOption.m

@@ -0,0 +1,12 @@
+//
+//  CoolIjkOption.m
+//  flutter_ijkplayer
+//
+//  Created by Caijinglong on 2019/4/12.
+//
+
+#import "CoolIjkOption.h"
+
+@implementation CoolIjkOption
+
+@end

+ 2 - 2
ios/Classes/CoolVideoInfo.h

@@ -18,7 +18,7 @@
 @property(nonatomic, assign) int degree;
 
 /**
- * Unit is KB.
+ * Unit is Byte.
  */
 @property(nonatomic, assign) int64_t tcpSpeed;
 
@@ -26,4 +26,4 @@
 
 - (NSDictionary *)toMap;
 
-@end
+@end

+ 1 - 1
ios/Classes/IjkplayerPlugin.m

@@ -57,7 +57,7 @@ static IjkplayerPlugin *__sharedInstance;
     dispatch_async(mainQueue, ^{
         if ([@"create" isEqualToString:call.method]) {
             @try {
-                int64_t id = [self->manager create];
+                int64_t id = [self->manager createWithCall:call];
                 result(@(id));
             }
             @catch (NSException *exception) {

+ 1 - 1
ios/flutter_ijkplayer.podspec

@@ -15,7 +15,7 @@ A new flutter plugin project.
   s.source_files = 'Classes/**/*' , 'IJKMediaFramework.framework'
   s.public_header_files = 'Classes/**/*.h'
   s.dependency 'Flutter'
-
+  s.static_framework = true
   s.ios.deployment_target = '8.0'
 
   # s.ios.vendored_frameworks = 'IJKMediaFramework.framework'

+ 1 - 0
lib/flutter_ijkplayer.dart

@@ -1,6 +1,7 @@
 export 'src/error.dart';
 export 'src/ijkplayer.dart';
 export 'src/entity/video_info.dart';
+export 'src/entity/options.dart';
 export 'src/widget/controller_widget_builder.dart'
     show DefaultIJKControllerWidget, VolumeType;
 export 'src/helper/config.dart';

+ 61 - 277
lib/src/controller/controller.dart

@@ -1,137 +1,37 @@
 part of '../ijkplayer.dart';
 
 /// Media Controller
-class IjkMediaController with IjkMediaControllerMixin {
-  /// MediaController
-  IjkMediaController({
-    this.autoRotate = true,
-  }) {
-    index = IjkMediaPlayerManager().add(this);
-  }
-
-  int index;
-
-  String get debugLabel => index.toString();
-
-  /// texture id from native
-  int _textureId;
-
+class IjkMediaController
+    with IjkMediaControllerMixin, IjkMediaControllerStreamMixin {
   /// It will automatically correct the direction of the video.
   bool autoRotate;
 
-  /// texture id from native
-  int get textureId => _textureId;
-
-  /// set texture id, Normally the user does not call
-  set textureId(int id) {
-    _textureId = id;
-    _textureIdController.add(id);
-  }
-
-  /// on texture id change
-  StreamController<int> _textureIdController = StreamController.broadcast();
-
-  /// on texture id change
-  Stream<int> get textureIdStream => _textureIdController?.stream;
-
-  /// Channel of flutter and native.
-  _IjkPlugin _plugin;
-
-  /// Whether texture id is null
-  bool get isInit => textureId == null;
-
-  /// channel of native to flutter
-  _IJKEventChannel eventChannel;
-
-  /// playing state
-  bool _isPlaying = false;
-
-  /// playing state
-  bool get isPlaying => _isPlaying == true;
-
-  /// playing state
-  set isPlaying(bool value) {
-    this._isPlaying = value;
-    _playingController?.add(value);
-    if (value == true) {
-      _ijkStatus = IjkStatus.playing;
-    }
-  }
-
-  /// playing state stream controller
-  StreamController<bool> _playingController = StreamController.broadcast();
-
-  /// playing state stream
-  Stream<bool> get playingStream => _playingController?.stream;
-
-  /// video info stream controller
-  StreamController<VideoInfo> _videoInfoController =
-      StreamController.broadcast();
-
-  /// video info stream
-  Stream<VideoInfo> get videoInfoStream => _videoInfoController?.stream;
-
-  VideoInfo _videoInfo = VideoInfo.fromMap(null);
-
-  /// last update video info.
-  VideoInfo get videoInfo => _videoInfo;
-
-  /// video volume stream controller
-  StreamController<int> _volumeController = StreamController.broadcast();
-
-  /// video volume stream
-  Stream<int> get volumeStream => _volumeController?.stream;
-
-  /// video volume, not system volume
-  int _volume = 100;
-
-  /// video volume, not system volume
-  set volume(int value) {
-    if (value > 100) {
-      value = 100;
-    } else if (value < 0) {
-      value = 0;
-    }
-    this._volume = value;
-    _volumeController?.add(value);
-    _setVolume(value);
-  }
-
-  /// video volume, not system volume
-  int get volume => _volume;
-
-  /// playFinish
-  StreamController<IjkMediaController> _playFinishController =
-      StreamController.broadcast();
-
-  /// On play finish
-  Stream<IjkMediaController> get playFinishStream =>
-      _playFinishController.stream;
+  int index;
 
-  IjkStatus __ijkStatus = IjkStatus.noDatasource;
+  String get debugLabel => index.toString();
 
-  IjkStatus get ijkStatus => __ijkStatus;
+  Map<TargetPlatform, Set<IjkOption>> _options = {};
 
-  set _ijkStatus(IjkStatus status) {
-    if (status != __ijkStatus) {
-      __ijkStatus = status;
-      _ijkStatusController?.add(status);
-    } else {
-      __ijkStatus = status;
-    }
+  /// MediaController
+  IjkMediaController({
+    this.autoRotate = true,
+  }) {
+    index = IjkMediaPlayerManager().add(this);
   }
 
-  /// 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 {
-      var id = await _createIjk();
+      List<IjkOption> options = [];
+      if (Platform.isAndroid) {
+        var opt = _options[TargetPlatform.android] ?? Set();
+        options.addAll(opt);
+      } else {
+        var opt = _options[TargetPlatform.iOS] ?? Set();
+        options.addAll(opt);
+      }
+
+      var id = await _createIjk(options: options);
       this.textureId = id;
       _plugin = _IjkPlugin(id);
       eventChannel = _IJKEventChannel(this);
@@ -146,20 +46,7 @@ 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;
+    await _disposeStream();
 
     IjkMediaPlayerManager().remove(this);
   }
@@ -176,6 +63,8 @@ class IjkMediaController with IjkMediaControllerMixin {
   }
 
   /// set net DataSource
+  ///
+  /// see [setDataSource]
   Future<void> setNetworkDataSource(
     String url, {
     Map<String, String> headers = const {},
@@ -192,6 +81,8 @@ class IjkMediaController with IjkMediaControllerMixin {
   }
 
   /// set asset DataSource
+  ///
+  /// see [setDataSource]
   Future<void> setAssetDataSource(
     String name, {
     String package,
@@ -204,6 +95,20 @@ class IjkMediaController with IjkMediaControllerMixin {
     }, autoPlay);
   }
 
+  /// set file DataSource
+  ///
+  /// see [setDataSource]
+  Future<void> setFileDataSource(
+    File file, {
+    bool autoPlay = false,
+  }) async {
+    _ijkStatus = IjkStatus.preparing;
+    await _initDataSource(() async {
+      await _plugin?.setFileDataSource(file.absolute.path);
+      _ijkStatus = IjkStatus.prepared;
+    }, autoPlay);
+  }
+
   /// Set datasource with [DataSource]
   Future<void> setDataSource(
     DataSource source, {
@@ -234,18 +139,6 @@ class IjkMediaController with IjkMediaControllerMixin {
     }
   }
 
-  /// set file DataSource
-  Future<void> setFileDataSource(
-    File file, {
-    bool autoPlay = false,
-  }) async {
-    _ijkStatus = IjkStatus.preparing;
-    await _initDataSource(() async {
-      await _plugin?.setFileDataSource(file.absolute.path);
-      _ijkStatus = IjkStatus.prepared;
-    }, autoPlay);
-  }
-
   /// dispose last textureId resource
   Future<void> _initDataSource(
     Future setDataSource(),
@@ -391,142 +284,33 @@ class IjkMediaController with IjkMediaControllerMixin {
   Future<Uint8List> screenShot() {
     return _plugin.screenShot();
   }
-}
-
-/// about channel
-MethodChannel _globalChannel = MethodChannel("top.kikt/ijkplayer");
-
-Future<int> _createIjk() async {
-  int id = await _globalChannel.invokeMethod("create");
-  return id;
-}
-
-class _IjkPlugin {
-  MethodChannel get channel => MethodChannel("top.kikt/ijkplayer/$textureId");
-
-  /// texture id
-  int textureId;
-
-  _IjkPlugin(this.textureId);
-
-  Future<void> dispose() async {
-    await _globalChannel.invokeMethod("dispose", {"id": textureId});
-  }
-
-  Future<void> play() async {
-    await channel.invokeMethod("play");
-  }
-
-  Future<void> pause() async {
-    await channel.invokeMethod("pause");
-  }
-
-  Future<void> stop() async {
-    await channel.invokeMethod("stop");
-  }
-
-  Future<void> setNetworkDataSource(
-      {String uri, Map<String, String> headers = const {}}) async {
-    LogUtils.debug("id = $textureId net uri = $uri ,headers = $headers");
-    await channel.invokeMethod("setNetworkDataSource", <String, dynamic>{
-      "uri": uri,
-      "headers": headers,
-    });
-  }
-
-  Future<void> setAssetDataSource(String name, String package) async {
-    LogUtils.debug("id = $textureId asset name = $name package = $package");
-    var params = <String, dynamic>{
-      "name": name,
-    };
-    if (package != null) {
-      params["package"] = package;
-    }
-    await channel.invokeMethod("setAssetDataSource", params);
-  }
-
-  Future<void> setFileDataSource(String path) async {
-    if (!File(path).existsSync()) {
-      return Error.fileNotExists;
-    }
-    await channel.invokeMethod("setFileDataSource", <String, dynamic>{
-      "path": path,
-    });
-    LogUtils.debug("id = $textureId file path = $path");
-  }
 
-  Future<Map<String, dynamic>> getInfo() async {
-    var map = await channel.invokeMethod("getInfo");
-    if (map == null) {
-      return null;
-    } else {
-      return map.cast<String, dynamic>();
+  /// Set [IjkOption] with IJKPlayer.
+  ///
+  /// It will only take effect if you call [setDataSource] again.
+  void setIjkPlayerOptions(
+    List<TargetPlatform> platforms,
+    Set<IjkOption> options,
+  ) {
+    for (var platform in platforms) {
+      _options[platform] = options;
     }
   }
 
-  Future<void> seekTo(double target) async {
-    await channel.invokeMethod("seekTo", <String, dynamic>{
-      "target": target,
-    });
-  }
-
+  /// Add [IjkOption] with IJKPlayer in native
   ///
-  Future<void> setVolume(int volume) async {
-    await channel.invokeMethod("setVolume", <String, dynamic>{
-      "volume": volume,
-    });
-  }
-
-  Future<Uint8List> screenShot() async {
-    var result = await channel.invokeMethod("screenShot");
-    if (result == null) {
-      return null;
+  /// see [setIjkPlayerOptions]
+  void addIjkPlayerOptions(
+    List<TargetPlatform> platforms,
+    Iterable<IjkOption> options,
+  ) {
+    for (var platform in platforms) {
+      var opts = _options[platform];
+      if (opts == null) {
+        opts = Set();
+        _options[platform] = opts;
+      }
+      opts.addAll(options);
     }
-    return result;
-  }
-}
-
-/// Entity classe for data sources.
-class DataSource {
-  /// See [DataSourceType]
-  DataSourceType _type;
-
-  File _file;
-
-  String _assetName;
-
-  String _assetPackage;
-
-  String _netWorkUrl;
-
-  Map<String, String> _headers;
-
-  DataSource._();
-
-  /// Create file data source
-  factory DataSource.file(File file) {
-    var ds = DataSource._();
-    ds._file = file;
-    ds._type = DataSourceType.file;
-    return ds;
-  }
-
-  /// Create network data source
-  factory DataSource.network(String url,
-      {Map<String, String> headers = const {}}) {
-    var ds = DataSource._();
-    ds._netWorkUrl = url;
-    ds._headers = headers;
-    ds._type = DataSourceType.network;
-    return ds;
-  }
-
-  /// Create asset data source
-  factory DataSource.asset(String assetName, {String package}) {
-    var ds = DataSource._();
-    ds._assetName = assetName;
-    ds._assetPackage = package;
-    ds._type = DataSourceType.asset;
-    return ds;
   }
 }

+ 46 - 0
lib/src/controller/datasoure.dart

@@ -0,0 +1,46 @@
+part of '../ijkplayer.dart';
+
+/// Entity classe for data sources.
+class DataSource {
+  /// See [DataSourceType]
+  DataSourceType _type;
+
+  File _file;
+
+  String _assetName;
+
+  String _assetPackage;
+
+  String _netWorkUrl;
+
+  Map<String, String> _headers;
+
+  DataSource._();
+
+  /// Create file data source
+  factory DataSource.file(File file) {
+    var ds = DataSource._();
+    ds._file = file;
+    ds._type = DataSourceType.file;
+    return ds;
+  }
+
+  /// Create network data source
+  factory DataSource.network(String url,
+      {Map<String, String> headers = const {}}) {
+    var ds = DataSource._();
+    ds._netWorkUrl = url;
+    ds._headers = headers;
+    ds._type = DataSourceType.network;
+    return ds;
+  }
+
+  /// Create asset data source
+  factory DataSource.asset(String assetName, {String package}) {
+    var ds = DataSource._();
+    ds._assetName = assetName;
+    ds._assetPackage = package;
+    ds._type = DataSourceType.asset;
+    return ds;
+  }
+}

+ 134 - 1
lib/src/controller/ijkplayer_controller_mixin.dart

@@ -1,4 +1,4 @@
-import 'package:flutter/material.dart';
+part of '../ijkplayer.dart';
 
 mixin IjkMediaControllerMixin {
   List<GlobalKey> _keys = [];
@@ -13,3 +13,136 @@ mixin IjkMediaControllerMixin {
     _keys.remove(key);
   }
 }
+
+mixin IjkMediaControllerStreamMixin {
+  /// texture id from native
+  int _textureId;
+
+  /// texture id from native
+  int get textureId => _textureId;
+
+  /// set texture id, Normally the user does not call
+  set textureId(int id) {
+    _textureId = id;
+    _textureIdController.add(id);
+  }
+
+  /// on texture id change
+  StreamController<int> _textureIdController = StreamController.broadcast();
+
+  /// on texture id change
+  Stream<int> get textureIdStream => _textureIdController?.stream;
+
+  /// Channel of flutter and native.
+  _IjkPlugin _plugin;
+
+  /// Whether texture id is null
+  bool get isInit => textureId == null;
+
+  /// channel of native to flutter
+  _IJKEventChannel eventChannel;
+
+  /// playing state
+  bool _isPlaying = false;
+
+  /// playing state
+  bool get isPlaying => _isPlaying == true;
+
+  /// playing state
+  set isPlaying(bool value) {
+    this._isPlaying = value;
+    _playingController?.add(value);
+    if (value == true) {
+      _ijkStatus = IjkStatus.playing;
+    }
+  }
+
+  /// playing state stream controller
+  StreamController<bool> _playingController = StreamController.broadcast();
+
+  /// playing state stream
+  Stream<bool> get playingStream => _playingController?.stream;
+
+  /// video info stream controller
+  StreamController<VideoInfo> _videoInfoController =
+      StreamController.broadcast();
+
+  /// video info stream
+  Stream<VideoInfo> get videoInfoStream => _videoInfoController?.stream;
+
+  VideoInfo _videoInfo = VideoInfo.fromMap(null);
+
+  /// last update video info.
+  VideoInfo get videoInfo => _videoInfo;
+
+  /// video volume stream controller
+  StreamController<int> _volumeController = StreamController.broadcast();
+
+  /// video volume stream
+  Stream<int> get volumeStream => _volumeController?.stream;
+
+  /// video volume, not system volume
+  int _volume = 100;
+
+  /// video volume, not system volume
+  set volume(int value) {
+    if (value > 100) {
+      value = 100;
+    } else if (value < 0) {
+      value = 0;
+    }
+    this._volume = value;
+    _volumeController?.add(value);
+    _setVolume(value);
+  }
+
+  /// video volume, not system volume
+  int get volume => _volume;
+
+  /// playFinish
+  StreamController<IjkMediaController> _playFinishController =
+      StreamController.broadcast();
+
+  /// On play finish
+  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;
+
+  void _setVolume(int value);
+
+  Future<void> _disposeStream() async {
+    _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;
+  }
+}

+ 111 - 0
lib/src/controller/plugin.dart

@@ -0,0 +1,111 @@
+part of '../ijkplayer.dart';
+
+/// about channel
+MethodChannel _globalChannel = MethodChannel("top.kikt/ijkplayer");
+
+Future<int> _createIjk({
+  List<IjkOption> options,
+}) async {
+  List<Map<String, dynamic>> _optionList = [];
+
+  for (var option in options) {
+    _optionList.add(option.toMap());
+  }
+
+  int id = await _globalChannel.invokeMethod(
+    "create",
+    <String, dynamic>{
+      "options": _optionList,
+    },
+  );
+  return id;
+}
+
+class _IjkPlugin {
+  MethodChannel get channel => MethodChannel("top.kikt/ijkplayer/$textureId");
+
+  /// texture id
+  int textureId;
+
+  _IjkPlugin(this.textureId);
+
+  Future<void> dispose() async {
+    await _globalChannel.invokeMethod("dispose", {"id": textureId});
+  }
+
+  Future<void> play() async {
+    await channel.invokeMethod("play");
+  }
+
+  Future<void> pause() async {
+    await channel.invokeMethod("pause");
+  }
+
+  Future<void> stop() async {
+    await channel.invokeMethod("stop");
+  }
+
+  Future<void> setNetworkDataSource(
+      {String uri, Map<String, String> headers = const {}}) async {
+    LogUtils.debug("id = $textureId net uri = $uri ,headers = $headers");
+    await channel.invokeMethod("setNetworkDataSource", <String, dynamic>{
+      "uri": uri,
+      "headers": headers,
+    });
+  }
+
+  Future<void> setAssetDataSource(String name, String package) async {
+    LogUtils.debug("id = $textureId asset name = $name package = $package");
+    var params = <String, dynamic>{
+      "name": name,
+    };
+    if (package != null) {
+      params["package"] = package;
+    }
+    await channel.invokeMethod("setAssetDataSource", params);
+  }
+
+  Future<void> setFileDataSource(String path) async {
+    if (!File(path).existsSync()) {
+      return Error.fileNotExists;
+    }
+    await channel.invokeMethod("setFileDataSource", <String, dynamic>{
+      "path": path,
+    });
+    LogUtils.debug("id = $textureId file path = $path");
+  }
+
+  Future<Map<String, dynamic>> getInfo() async {
+    try {
+      var map = await channel.invokeMethod("getInfo");
+      if (map == null) {
+        return null;
+      } else {
+        return map.cast<String, dynamic>();
+      }
+    } on Exception {
+      return null;
+    }
+  }
+
+  Future<void> seekTo(double target) async {
+    await channel.invokeMethod("seekTo", <String, dynamic>{
+      "target": target,
+    });
+  }
+
+  ///
+  Future<void> setVolume(int volume) async {
+    await channel.invokeMethod("setVolume", <String, dynamic>{
+      "volume": volume,
+    });
+  }
+
+  Future<Uint8List> screenShot() async {
+    var result = await channel.invokeMethod("screenShot");
+    if (result == null) {
+      return null;
+    }
+    return result;
+  }
+}

+ 47 - 0
lib/src/entity/options.dart

@@ -0,0 +1,47 @@
+/// see [bilibili/ijkplayer](https://github.com/bilibili/ijkplayer)
+enum IjkOptionCategory {
+  format,
+  codec,
+  sws,
+  player,
+}
+
+/// see [bilibili/ijkplayer](https://github.com/bilibili/ijkplayer)
+class IjkOption {
+  /// see [bilibili/ijkplayer](https://github.com/bilibili/ijkplayer)
+  final IjkOptionCategory category;
+
+  /// see [bilibili/ijkplayer](https://github.com/bilibili/ijkplayer)
+  final String key;
+
+  /// see [bilibili/ijkplayer](https://github.com/bilibili/ijkplayer)
+  final dynamic value;
+
+  IjkOption(this.category, this.key, this.value);
+
+  /// to map
+  Map<String, dynamic> toMap() {
+    return {
+      "category": category.index,
+      "key": key,
+      "value": value,
+    };
+  }
+
+  @override
+  int get hashCode => category.hashCode + key.hashCode;
+
+  @override
+  bool operator ==(other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    if (other == null) {
+      return false;
+    }
+    if (other is! IjkOption) {
+      return false;
+    }
+    return this.category == other.category && this.key == other.key;
+  }
+}

+ 4 - 1
lib/src/ijkplayer.dart

@@ -5,8 +5,8 @@ import 'dart:typed_data';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
-import 'controller/ijkplayer_controller_mixin.dart';
 import 'entity/video_info.dart';
+import 'entity/options.dart';
 import 'engine/ijk_controller_manager.dart';
 import 'error.dart';
 import 'helper/logutil.dart';
@@ -15,8 +15,11 @@ import 'widget/ijkplayer_builder.dart';
 import 'widget/ijk_status_widget.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

+ 1 - 1
pubspec.yaml

@@ -1,6 +1,6 @@
 name: flutter_ijkplayer
 description: Flutter version of bilibilibili ijkplayer, supports common playback protocols, easy to use.
-version: 0.2.1
+version: 0.2.2
 author: caijinglong<cjl_spy@163.com>
 homepage: https://github.com/CaiJingLong/flutter_ijkplayer