瀏覽代碼

整合Video 到 flutter

ykrank 5 年之前
父節點
當前提交
0017271c28

+ 2
- 2
android/build.gradle 查看文件

@@ -2,7 +2,7 @@ group 'com.shuangyubang.mofun_flutter_plugin_video'
2 2
 version '1.0-SNAPSHOT'
3 3
 
4 4
 buildscript {
5
-    ext.kotlin_version = '1.3.21'
5
+    ext.kotlin_version = '1.3.31'
6 6
     repositories {
7 7
         google()
8 8
         jcenter()
@@ -25,7 +25,7 @@ apply plugin: 'com.android.library'
25 25
 apply plugin: 'kotlin-android'
26 26
 
27 27
 android {
28
-    compileSdkVersion 27
28
+    compileSdkVersion 28
29 29
 
30 30
     sourceSets {
31 31
         main.java.srcDirs += 'src/main/kotlin'

+ 6
- 6
android/src/main/java/com/fgodt/mixplayer/Player.java 查看文件

@@ -12,12 +12,12 @@ import java.util.ArrayList;
12 12
 public class Player extends Render {
13 13
 
14 14
 
15
-    public final int STATUS_ERROR = 0;
16
-    public final int STATUS_STOP = 1;
17
-    public final int STATUS_START = 2;
18
-    public final int STATUS_PAUSE = 3;
19
-    public final int STATUS_BUFFING = 4;
20
-    public final int STATUS_RUNNING = 5;
15
+    public static final int STATUS_ERROR = 0;
16
+    public static final int STATUS_STOP = 1;
17
+    public static final int STATUS_START = 2;
18
+    public static final int STATUS_PAUSE = 3;
19
+    public static final int STATUS_BUFFING = 4;
20
+    public static final int STATUS_RUNNING = 5;
21 21
 
22 22
     private Core mixCore;
23 23
     private Core enMixCore;

+ 57
- 56
android/src/main/java/com/fgodt/mixplayer/Render.java 查看文件

@@ -1,21 +1,13 @@
1 1
 package com.fgodt.mixplayer;
2 2
 
3 3
 import android.content.Context;
4
-import android.content.res.Configuration;
5
-import android.media.AudioAttributes;
6 4
 import android.media.AudioFormat;
7 5
 import android.media.AudioManager;
8 6
 import android.media.AudioTrack;
9
-import android.opengl.GLSurfaceView;
10
-import android.support.v7.app.AppCompatActivity;
7
+import android.os.Build;
11 8
 import android.util.Log;
12
-import android.widget.FrameLayout;
13 9
 
14
-import java.sql.Time;
15
-import java.util.Arrays;
16
-import java.util.Date;
17
-
18
-import static java.lang.Thread.*;
10
+import static java.lang.Thread.sleep;
19 11
 
20 12
 /**
21 13
  * Created by rt-zl on 2019/2/15.
@@ -24,7 +16,6 @@ import static java.lang.Thread.*;
24 16
 public class Render extends VideoRender {
25 17
 
26 18
 
27
-
28 19
     static {
29 20
         System.loadLibrary("mixplayer");
30 21
     }
@@ -34,24 +25,25 @@ public class Render extends VideoRender {
34 25
     private native int setRenderHandler(long handler);
35 26
 
36 27
     int asample, ach, abit;
37
-    public int mixUpdateAudio(byte[] data, int size,double pts){
28
+
29
+    public int mixUpdateAudio(byte[] data, int size, double pts) {
38 30
         playTime = pts;
39 31
         synchronized (audioBuf) {
40 32
             if (audioBufLen - audioHas < size) {
41 33
                 return -1;
42 34
             }
43 35
             int len = audioBufLen - audioWPos;
44
-            len = len > size ? size :len;
45
-            System.arraycopy(data,0,audioBuf,audioWPos,len);
36
+            len = len > size ? size : len;
37
+            System.arraycopy(data, 0, audioBuf, audioWPos, len);
46 38
             audioWPos += len;
47
-            audioHas +=len;
48
-            if(audioWPos == audioBufLen){
39
+            audioHas += len;
40
+            if (audioWPos == audioBufLen) {
49 41
                 audioWPos = 0;
50 42
             }
51
-            if(len<size){
43
+            if (len < size) {
52 44
                 int les = size - len;
53
-                System.arraycopy(data,len,audioBuf,audioWPos,les);
54
-                audioWPos +=les;
45
+                System.arraycopy(data, len, audioBuf, audioWPos, les);
46
+                audioWPos += les;
55 47
                 audioHas += les;
56 48
             }
57 49
             return 0;
@@ -59,62 +51,63 @@ public class Render extends VideoRender {
59 51
     }
60 52
 
61 53
 
62
-    public int mixOpen(int vWidth,int vHeight,int asample, int ach, int abit){
63
-        if(vWidth>0){
64
-            Log.d("MIXOPEN","has video");
65
-            nativeInit(getScreenWidth(),getScreenHeight());
54
+    public int mixOpen(int vWidth, int vHeight, int asample, int ach, int abit) {
55
+        if (vWidth > 0) {
56
+            Log.d("MIXOPEN", "has video");
57
+            nativeInit(getScreenWidth(), getScreenHeight());
66 58
             this.abit = abit;
67 59
             this.asample = asample;
68 60
             this.ach = ach;
69 61
             initAudioTrack();
70
-            return  0;
62
+            return 0;
71 63
         }
72 64
         return -1;
73 65
     }
74 66
 
75 67
 
76
-    public void mixClose(){
68
+    public void mixClose() {
77 69
         super.close();
78 70
     }
79 71
 
80
-    public void renderClose(){
72
+    public void renderClose() {
81 73
         play = false;
82
-        if (audioTrack!=null){
74
+        if (audioTrack != null) {
83 75
             audioTrack.stop();
84 76
             totalAudioBuf = 0;
85 77
         }
86 78
     }
87 79
 
88
-    public Render(Context context){
80
+    public Render(Context context) {
89 81
         super(context);
90 82
     }
91 83
 
92
-    public void init(long handler){
84
+    public void init(long handler) {
93 85
         this.handler = handler;
94 86
         setMixHander(handler);
95 87
         this.setRenderHandler(handler);
96 88
     }
97 89
 
98 90
     public double getPlaytime() {
99
-        return  playTime;
91
+        return playTime;
100 92
     }
101 93
 
102
-    AudioTrack audioTrack ;
94
+    AudioTrack audioTrack;
103 95
 
104
-    byte[] audioBuf ;
96
+    byte[] audioBuf;
105 97
     long totalAudioBuf;
106 98
     int audioHas;
107 99
     int audioRPos, audioWPos;
108
-    int audioBufLen = 4096*3;
100
+    int audioBufLen = 4096 * 3;
109 101
     long silenceLen = 0;
110 102
     boolean play = false;
111 103
     double playTime = 0;
112 104
     byte[] silencBuf;
113
-    private void initAudioTrack(){
105
+
106
+    private void initAudioTrack() {
114 107
         silencBuf = new byte[4096];
115 108
         audioBuf = new byte[audioBufLen];
116
-        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,asample,
117
-                AudioFormat.CHANNEL_IN_STEREO,AudioFormat.ENCODING_PCM_16BIT,audioBufLen,AudioTrack.MODE_STREAM);
109
+        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, asample,
110
+                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, audioBufLen, AudioTrack.MODE_STREAM);
118 111
         play = true;
119 112
         new Thread(new Runnable() {
120 113
             @Override
@@ -129,49 +122,57 @@ public class Render extends VideoRender {
129 122
                         // 16bit 2channels = 4 Byte
130 123
                         pos *= 4;
131 124
                         pos -= silenceLen;
132
-                        if(posPre != pos){
125
+                        if (posPre != pos) {
133 126
                             posPre = pos;
134 127
                             retryCount = 0;
135
-                        }else {
136
-                            if(retryCount > 5)
128
+                        } else {
129
+                            if (retryCount > 5)
137 130
                                 needUpdate = true;
138
-                            retryCount ++;
131
+                            retryCount++;
139 132
                         }
140
-                        if (audioHas >= wsize && totalAudioBuf - pos < audioBufLen || pos <= 0 ||needUpdate ) {
133
+                        if (audioHas >= wsize && totalAudioBuf - pos < audioBufLen || pos <= 0 || needUpdate) {
141 134
                             needUpdate = false;
142 135
                             int len = audioBufLen - audioRPos;
143
-                            len = len > wsize? wsize:len;
136
+                            len = len > wsize ? wsize : len;
144 137
                             int ret = audioTrack.write(audioBuf, audioRPos, len);
145 138
                             audioRPos += ret;
146 139
                             audioHas -= ret;
147
-                            totalAudioBuf+=ret;
140
+                            totalAudioBuf += ret;
148 141
                             if (audioRPos == audioBufLen) {
149 142
                                 audioRPos = 0;
150 143
                             }
151
-                        }else {
152
-                        //    if (pos == totalAudioBuf && audioHas < wsize){
153
-                        //        int c = audioTrack.write(silencBuf,0,2048);
154
-                        //        silenceLen +=c;
155
-                        //    }
144
+                        } else {
145
+                            //    if (pos == totalAudioBuf && audioHas < wsize){
146
+                            //        int c = audioTrack.write(silencBuf,0,2048);
147
+                            //        silenceLen +=c;
148
+                            //    }
156 149
                         }
157
-                        if(ispause){
158
-                            if(audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PAUSED) {
150
+                        if (ispause) {
151
+                            if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PAUSED) {
159 152
                                 audioTrack.pause();
160
-                                audioTrack.setVolume(0);
153
+                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
154
+                                    audioTrack.setVolume(0);
155
+                                } else {
156
+                                    audioTrack.setStereoVolume(0, 0);
157
+                                }
161 158
                             }
162
-                        }else{
163
-                            if(audioTrack.getPlayState()!=AudioTrack.PLAYSTATE_PLAYING) {
159
+                        } else {
160
+                            if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
164 161
                                 audioTrack.play();
165
-                                audioTrack.setVolume(volume);
162
+                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
163
+                                    audioTrack.setVolume(volume);
164
+                                } else {
165
+                                    audioTrack.setStereoVolume(volume, volume);
166
+                                }
166 167
                             }
167 168
                         }
168 169
                     }
169 170
                     try {
170 171
                         sleep(3);
171
-                    }catch (Exception ex){
172
+                    } catch (Exception ex) {
172 173
                     }
173 174
                 }
174
-                Log.d("mixplayer","audio close");
175
+                Log.d("mixplayer", "audio close");
175 176
             }
176 177
         }).start();
177 178
         audioTrack.play();

+ 0
- 252
android/src/main/java/com/fogdt/mixsample/MainActivity.java 查看文件

@@ -1,252 +0,0 @@
1
-package com.fogdt.mixsample;
2
-
3
-import android.Manifest;
4
-import android.app.ActivityManager;
5
-import android.app.AlertDialog;
6
-import android.content.DialogInterface;
7
-import android.content.pm.PackageManager;
8
-import android.content.res.Configuration;
9
-import android.media.AudioFormat;
10
-import android.media.AudioRecord;
11
-import android.media.MediaCodecInfo;
12
-import android.media.MediaRecorder;
13
-import android.os.Environment;
14
-import android.os.PersistableBundle;
15
-import android.support.annotation.Nullable;
16
-import android.support.v4.app.ActivityCompat;
17
-import android.support.v4.content.ContextCompat;
18
-import android.support.v7.app.AppCompatActivity;
19
-import android.os.Bundle;
20
-import android.text.TextPaint;
21
-import android.util.DisplayMetrics;
22
-import android.util.Log;
23
-import android.view.Gravity;
24
-import android.view.SurfaceHolder;
25
-import android.view.SurfaceView;
26
-import android.view.View;
27
-import android.view.ViewGroup;
28
-import android.view.WindowManager;
29
-import android.widget.EditText;
30
-import android.widget.FrameLayout;
31
-import android.widget.TextView;
32
-import android.widget.Toast;
33
-
34
-import com.fgodt.mixplayer.Core;
35
-import com.fgodt.mixplayer.Player;
36
-import com.fgodt.mixplayer.mixPlayerCallBack;
37
-
38
-import java.io.File;
39
-
40
-
41
-public class MainActivity extends AppCompatActivity implements mixPlayerCallBack {
42
-
43
-
44
-    private long handler =0;
45
-
46
-    AudioRecord audioRecord = null;
47
-    @Override
48
-    protected void onCreate(Bundle savedInstanceState) {
49
-        super.onCreate(savedInstanceState);
50
-
51
-
52
-
53
-        setContentView(R.layout.activity_main);
54
-
55
-
56
-
57
-        // Example of a call to a native method
58
-        TextView tv = (TextView) findViewById(R.id.sample_text);
59
-
60
-        tv.setText("el");
61
-        getPermission();
62
-
63
-
64
-        if(savedInstanceState == null)
65
-            test();
66
-    }
67
-
68
-    @Override
69
-    public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
70
-        super.onWindowAttributesChanged(params);
71
-    }
72
-
73
-    private Player mixplayer ;
74
-    private void test(){
75
-
76
-        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.FILL_PARENT,
77
-                FrameLayout.LayoutParams.WRAP_CONTENT);
78
-        params.gravity = Gravity.TOP;
79
-        params.height = 1080*9/16;
80
-        mixplayer = new Player(this);
81
-        mixplayer.setMixCall(this);
82
-        mixplayer.addMixSource("mix1",0,-1);
83
-        mixplayer.addMixSource("silence",0,-1);
84
-       // mixplayer.selectAudioSource("silence");
85
-       // mixplayer.selectAudioSource("mix1");
86
-        addContentView(mixplayer,params);
87
-        editText = findViewById(R.id.editText);
88
-    }
89
-
90
-    private String getPath(){
91
-        String  file = Environment.getExternalStoragePublicDirectory(
92
-                Environment.DIRECTORY_MOVIES)+"" ;
93
-        return file;
94
-    }
95
-
96
-    private EditText editText;
97
-    public void playClick(View sender){
98
-        if(!hasRWPermission) {
99
-            getPermission();
100
-            return;
101
-        }
102
-        mixplayer.open(editText.getText().toString());
103
-    }
104
-
105
-    public void savedClick(View view){
106
-        //mixplayer.selectAudioSource(editText.getText().toString());
107
-        mixplayer.saveFile(editText.getText().toString(),getPath()+"/mixtest.mp4");
108
-        //mixplayer.saveFile(editText.getText().toString(),"rtmp://192.168.3.10/live/abc1234");
109
-    }
110
-
111
-    //10s pcm data
112
-    int totalLen = 4*44100*10;
113
-    byte[] pcmData = new byte[4*44100*10];
114
-    int pcmWritePos = 0;
115
-    public void closeClick(View sender){
116
-       mixplayer.stop();
117
-    }
118
-
119
-    public void recorderClick(View view){
120
-        if(!hasRcordPermission) {
121
-            getPermission();
122
-            return;
123
-        }
124
-
125
-
126
-        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,44100, AudioFormat.CHANNEL_IN_STEREO,AudioFormat.ENCODING_PCM_16BIT,4096*3);
127
-        if(audioRecord.getState() != AudioRecord.STATE_INITIALIZED){
128
-            Toast.makeText(this,"初始化录音失败",Toast.LENGTH_SHORT).show();
129
-            return;
130
-        }
131
-
132
-        if(audioRecord != null){
133
-            audioRecord.startRecording();
134
-            pcmWritePos = 0;
135
-            pcmReadPos = 0;
136
-            new Thread(new Runnable() {
137
-                @Override
138
-                public void run() {
139
-                    while (pcmWritePos<totalLen){
140
-                        int wlen = totalLen-pcmWritePos > 4096? 4096:totalLen-pcmWritePos;
141
-                        int ret = audioRecord.read(pcmData,pcmWritePos,wlen);
142
-                        pcmWritePos+=ret;
143
-                    }
144
-                    audioRecord.stop();
145
-                }
146
-            }).start();
147
-        }
148
-    }
149
-
150
-    public void playerAndResume(View view){
151
-        if(mixplayer.getStatus() == mixplayer.STATUS_PAUSE){
152
-            mixplayer.resume();
153
-        }else if(mixplayer.getStatus() == mixplayer.STATUS_RUNNING){
154
-            mixplayer.pause();
155
-        }
156
-    }
157
-
158
-    @Override
159
-    protected void onSaveInstanceState(Bundle outState) {
160
-        super.onSaveInstanceState(outState);
161
-    }
162
-
163
-
164
-    @Override
165
-    protected void onRestoreInstanceState(Bundle savedInstanceState) {
166
-        super.onRestoreInstanceState(savedInstanceState);
167
-    }
168
-
169
-
170
-    int pcmReadPos = 0;
171
-    @Override
172
-    public int mix_call(String name, byte[] data, int len) {
173
-        if(name.equals("silence")){
174
-            return 0;
175
-        }
176
-        if(totalLen - pcmReadPos < len){
177
-            return 0;
178
-        }else {
179
-            System.arraycopy(pcmData,pcmReadPos,data,0,len);
180
-            pcmReadPos += len;
181
-            return len;
182
-        }
183
-    }
184
-
185
-    @Override
186
-    public void closeMsg(String msg) {
187
-        Toast.makeText(this,"MIX CLOSE",Toast.LENGTH_SHORT).show();
188
-    }
189
-
190
-
191
-    private boolean hasRWPermission = false;
192
-    private boolean hasRcordPermission = false;
193
-
194
-    private  void getPermission(){
195
-        int has = ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
196
-        if (has == PackageManager.PERMISSION_GRANTED){
197
-            hasRWPermission = true;
198
-        }else {
199
-            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE  },1);
200
-        }
201
-
202
-        has = ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.RECORD_AUDIO);
203
-        if (has == PackageManager.PERMISSION_GRANTED){
204
-            hasRcordPermission = true;
205
-        }else {
206
-            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.RECORD_AUDIO},2);
207
-        }
208
-    }
209
-
210
-    @Override
211
-    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults){
212
-        if(requestCode == 1){
213
-            if(grantResults.length>0&&grantResults[0] == PackageManager.PERMISSION_GRANTED){
214
-                hasRWPermission = true;
215
-            }else {
216
-                if(ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)){
217
-                    new AlertDialog.Builder(this)
218
-                            .setMessage("need read write permisson")
219
-                            .setPositiveButton("ok",(d,w)->{
220
-                                if (w == DialogInterface.BUTTON_POSITIVE){
221
-                                    ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
222
-                                }
223
-                            })
224
-                            .setNegativeButton("cancle",null)
225
-                            .setTitle("需要提供相应权限")
226
-                            .create()
227
-                            .show();
228
-                }
229
-            }
230
-        }
231
-        if (requestCode == 2){
232
-            if(grantResults.length>0&&grantResults[0] == PackageManager.PERMISSION_GRANTED){
233
-                hasRcordPermission = true;
234
-            }else {
235
-                if(ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.RECORD_AUDIO)){
236
-                    new AlertDialog.Builder(this)
237
-                            .setMessage("need record audio permisson")
238
-                            .setPositiveButton("ok",(dialog,which)->{
239
-                                if (which == DialogInterface.BUTTON_POSITIVE){
240
-                                    ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.RECORD_AUDIO},1);
241
-                                }
242
-                            })
243
-                            .setNegativeButton("cancle",null)
244
-                            .setTitle("需要录音权限")
245
-                            .create()
246
-                            .show();
247
-                }
248
-            }
249
-        }
250
-    }
251
-
252
-}

+ 87
- 20
android/src/main/kotlin/com/shuangyubang/mofun_flutter_plugin_video/MofunRealVideoView.kt 查看文件

@@ -1,49 +1,116 @@
1 1
 package com.shuangyubang.mofun_flutter_plugin_video
2 2
 
3 3
 import android.content.Context
4
+import android.media.AudioFormat
5
+import android.media.AudioRecord
6
+import android.media.MediaRecorder
7
+import android.os.Environment
4 8
 import android.view.Gravity
5 9
 import android.view.View
6
-import android.widget.EditText
7 10
 import android.widget.FrameLayout
8
-import android.widget.LinearLayout
11
+import android.widget.Toast
9 12
 import com.fgodt.mixplayer.Player
10 13
 import com.fgodt.mixplayer.mixPlayerCallBack
11 14
 
12 15
 class MofunRealVideoView(val context: Context) : mixPlayerCallBack {
13 16
 
14
-    private var trueView:FrameLayout? = null
17
+    private var trueView: FrameLayout? = null
18
+    private var mixplayer: Player? = null
19
+    private var url: String? = null
15 20
 
16
-    fun getView(): View? {
21
+    //10s pcm data
22
+    private var totalLen = 4 * 44100 * 10
23
+    private var pcmData = ByteArray(4 * 44100 * 10)
24
+    private var pcmWritePos = 0
25
+
26
+    private var pcmReadPos = 0
27
+
28
+    fun getView(): View {
17 29
         synchronized(MofunRealVideoView::class) {
18
-            if (trueView == null){
30
+            if (trueView == null) {
19 31
                 initView()
20 32
             }
21 33
         }
22
-        return trueView
34
+        return trueView!!
35
+    }
36
+
37
+    fun dispose(){
38
+        mixplayer?.stop()
23 39
     }
24 40
 
25
-    private fun initView(){
41
+    private fun initView() {
26 42
         trueView = FrameLayout(context)
27 43
         val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
28
-                FrameLayout.LayoutParams.WRAP_CONTENT)
29
-        params.gravity = Gravity.TOP
30
-        params.height = 1080 * 9 / 16
31
-        val mixplayer = Player(context)
32
-        mixplayer.setMixCall(this)
33
-        mixplayer.addMixSource("mix1", 0.0, -1.0)
34
-        mixplayer.addMixSource("silence", 0.0, -1.0)
35
-        // mixplayer.selectAudioSource("silence");
36
-        // mixplayer.selectAudioSource("mix1");
37
-
38
-        trueView?.addView(mixplayer, params)
44
+                FrameLayout.LayoutParams.MATCH_PARENT)
45
+        mixplayer = Player(context)
46
+        mixplayer?.apply {
47
+            this.setMixCall(this@MofunRealVideoView)
48
+            this.addMixSource("mix1", 0.0, -1.0)
49
+            this.addMixSource("silence", 0.0, -1.0)
50
+            // mixplayer.selectAudioSource("silence");
51
+            // mixplayer.selectAudioSource("mix1");
39 52
 
53
+            trueView?.addView(this, params)
54
+        }
40 55
     }
41 56
 
42 57
     override fun mix_call(name: String?, data: ByteArray?, len: Int): Int {
43
-        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
58
+        if (name == "silence") {
59
+            return 0
60
+        }
61
+        if (totalLen - pcmReadPos < len) {
62
+            return 0
63
+        } else {
64
+            System.arraycopy(pcmData, pcmReadPos, data, 0, len)
65
+            pcmReadPos += len
66
+            return len
67
+        }
44 68
     }
45 69
 
46 70
     override fun closeMsg(msg: String?) {
47
-        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
71
+        Toast.makeText(context, "MIX CLOSE", Toast.LENGTH_SHORT).show()
72
+    }
73
+
74
+    fun play(url: String) {
75
+        this.url = url
76
+        mixplayer?.open(url)
77
+    }
78
+
79
+    fun save(output: String) {
80
+        if (url != null)
81
+            mixplayer?.saveFile(url, output)
82
+    }
83
+
84
+    fun stop() {
85
+        mixplayer?.stop()
86
+    }
87
+
88
+    fun record() {
89
+        val audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, 4096 * 3)
90
+        if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
91
+            Toast.makeText(context, "初始化录音失败", Toast.LENGTH_SHORT).show()
92
+            return
93
+        }
94
+
95
+        audioRecord.startRecording()
96
+        pcmWritePos = 0
97
+        pcmReadPos = 0
98
+        Thread(Runnable {
99
+            while (pcmWritePos < totalLen) {
100
+                val wlen = if (totalLen - pcmWritePos > 4096) 4096 else totalLen - pcmWritePos
101
+                val ret = audioRecord.read(pcmData, pcmWritePos, wlen)
102
+                pcmWritePos += ret
103
+            }
104
+            audioRecord.stop()
105
+        }).start()
106
+
107
+    }
108
+
109
+    fun playerAndResume() {
110
+        if (mixplayer?.status == Player.STATUS_PAUSE) {
111
+            mixplayer?.resume()
112
+        } else if (mixplayer?.status == Player.STATUS_RUNNING) {
113
+            mixplayer?.pause()
114
+        }
48 115
     }
49 116
 }

+ 40
- 9
android/src/main/kotlin/com/shuangyubang/mofun_flutter_plugin_video/MofunVideoView.kt 查看文件

@@ -4,6 +4,7 @@ import android.content.Context
4 4
 import android.graphics.Color
5 5
 import android.view.View
6 6
 import android.widget.TextView
7
+import android.widget.Toast
7 8
 import io.flutter.plugin.common.BinaryMessenger
8 9
 import io.flutter.plugin.common.MethodCall
9 10
 import io.flutter.plugin.common.MethodChannel
@@ -17,35 +18,65 @@ const val Channel_Mofun_Video_Info = "plugins.shuangyunbang.mofun/mofunvideoview
17 18
 class MofunVideoView
18 19
     : PlatformView, MethodChannel.MethodCallHandler {
19 20
 
20
-    private val textView: TextView
21
+    private val videoView: MofunRealVideoView
21 22
 
22 23
     constructor(context: Context, messenger: BinaryMessenger, id: Int, creationParams: Any?) {
23
-        textView = TextView(context)
24
-        textView.setBackgroundColor(Color.BLUE)
24
+        videoView = MofunRealVideoView(context)
25 25
         MethodChannel(messenger, Channel_Mofun_Video_Info + "_$id").setMethodCallHandler(this)
26 26
     }
27 27
 
28 28
     override fun getView(): View {
29
-        return textView
29
+        return videoView.getView()
30 30
     }
31 31
 
32 32
     override fun dispose() {
33
-
33
+        videoView.dispose()
34 34
     }
35 35
 
36 36
     override fun onMethodCall(methodCall: MethodCall, result: MethodChannel.Result) {
37 37
         when (methodCall.method) {
38
-            "setText" -> setText(methodCall, result)
38
+            "toast" -> toast(methodCall, result)
39
+            "play" -> play(methodCall, result)
40
+            "stop" -> stop(methodCall, result)
41
+            "save" -> save(methodCall, result)
42
+            "record" -> record(methodCall, result)
43
+            "playerAndResume" -> playerAndResume(methodCall, result)
39 44
             else -> result.notImplemented()
40 45
         }
41 46
     }
42 47
 
43
-    private fun setText(methodCall: MethodCall, result: MethodChannel.Result) {
44
-        val text = methodCall.arguments as String
45
-        textView.text = text
48
+    private fun toast(methodCall: MethodCall, result: MethodChannel.Result) {
49
+        val url = methodCall.arguments as String
50
+        Toast.makeText(videoView.context, url, Toast.LENGTH_SHORT).show()
51
+        result.success(null)
52
+    }
53
+
54
+    private fun play(methodCall: MethodCall, result: MethodChannel.Result) {
55
+        val url = methodCall.arguments as String
56
+        videoView.play(url)
46 57
         result.success(null)
47 58
     }
48 59
 
60
+    private fun stop(methodCall: MethodCall, result: MethodChannel.Result) {
61
+        videoView.stop()
62
+        result.success(null)
63
+    }
64
+
65
+    private fun save(methodCall: MethodCall, result: MethodChannel.Result) {
66
+        val output = methodCall.arguments as String
67
+        videoView.save(output)
68
+        result.success(null)
69
+    }
70
+
71
+    private fun record(methodCall: MethodCall, result: MethodChannel.Result) {
72
+        videoView.record()
73
+        result.success(null)
74
+    }
75
+
76
+    private fun playerAndResume(methodCall: MethodCall, result: MethodChannel.Result) {
77
+        videoView.playerAndResume()
78
+        result.success(null)
79
+    }
49 80
 }
50 81
 
51 82
 class MofunVideoViewFactory(val messenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {

+ 2
- 2
example/android/app/build.gradle 查看文件

@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
26 26
 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 27
 
28 28
 android {
29
-    compileSdkVersion 27
29
+    compileSdkVersion 28
30 30
 
31 31
     sourceSets {
32 32
         main.java.srcDirs += 'src/main/kotlin'
@@ -40,7 +40,7 @@ android {
40 40
         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 41
         applicationId "com.shuangyubang.mofun_flutter_plugin_video_example"
42 42
         minSdkVersion 16
43
-        targetSdkVersion 27
43
+        targetSdkVersion 28
44 44
         versionCode flutterVersionCode.toInteger()
45 45
         versionName flutterVersionName
46 46
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

+ 1
- 1
example/android/build.gradle 查看文件

@@ -1,5 +1,5 @@
1 1
 buildscript {
2
-    ext.kotlin_version = '1.3.21'
2
+    ext.kotlin_version = '1.3.31'
3 3
     repositories {
4 4
         google()
5 5
         jcenter()

+ 68
- 23
example/lib/main.dart 查看文件

@@ -43,31 +43,76 @@ class _MyAppState extends State<MyApp> {
43 43
 
44 44
   @override
45 45
   Widget build(BuildContext context) {
46
+    const String defaultUrl = 'rtmp://live.hkstv.hk.lxdns.com/live/hks1';
47
+
46 48
     return MaterialApp(
47 49
       home: Scaffold(
48
-        appBar: AppBar(
49
-          title: const Text('Plugin example app'),
50
-        ),
51
-        body: Column(
52
-          children: <Widget>[
53
-            Container(
54
-              alignment: Alignment.center,
55
-              height: 48,
56
-              child: Text('Running on: $_platformVersion'),
57
-            ),
58
-            Container(
59
-              height: 100.0,
60
-              child: MofunVideoView(
61
-                onMofunVideoViewCreated: _onMofunVideoViewCreated,
62
-              ),
63
-            ),
64
-          ],
65
-        ),
66
-      ),
50
+          appBar: AppBar(
51
+            title: const Text('Plugin example app'),
52
+          ),
53
+          body: Builder(builder: (context) {
54
+            TextEditingController textEditingController =
55
+                TextEditingController(text: defaultUrl);
56
+            MofunVideoViewController controller;
57
+            String url;
58
+
59
+            return Column(
60
+              children: <Widget>[
61
+                Container(
62
+                  height: 400.0,
63
+                  child: MofunVideoView(
64
+                    onMofunVideoViewCreated: (c) {
65
+                      controller = c;
66
+                      c.toast('Mofun video!');
67
+                    },
68
+                  ),
69
+                ),
70
+                TextField(
71
+                    controller: textEditingController,
72
+                    onChanged: (s) => url = s),
73
+                Row(
74
+                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
75
+                  children: <Widget>[
76
+                    RaisedButton(
77
+                      onPressed: () {
78
+                        controller?.play(url ?? defaultUrl);
79
+                      },
80
+                      child: Text('Play'),
81
+                    ),
82
+                    RaisedButton(
83
+                      onPressed: () {
84
+                        controller?.stop();
85
+                      },
86
+                      child: Text('Stop'),
87
+                    ),
88
+                    RaisedButton(
89
+                      onPressed: () {
90
+                        controller?.toast('暂不支持');
91
+                      },
92
+                      child: Text('Save'),
93
+                    ),
94
+                  ],
95
+                ),
96
+                Row(
97
+                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
98
+                  children: <Widget>[
99
+                    RaisedButton(
100
+                      onPressed: () {
101
+                        controller?.record();
102
+                      },
103
+                      child: Text('Record'),
104
+                    ),
105
+                    RaisedButton(
106
+                      onPressed: () {
107
+                        controller?.playerAndResume();
108
+                      },
109
+                      child: Text('PlayerAndResume'),
110
+                    ),
111
+                  ],
112
+                ),
113
+              ],
114
+            );
115
+          })),
67 116
     );
68 117
   }
69 118
 }
70
-
71
-void _onMofunVideoViewCreated(MofunVideoViewController controller) {
72
-  controller.setText('Hello from Android!');
73
-}

+ 7
- 0
example/pubspec.lock 查看文件

@@ -81,6 +81,13 @@ packages:
81 81
       url: "https://pub.flutter-io.cn"
82 82
     source: hosted
83 83
     version: "1.5.0"
84
+  permission_handler:
85
+    dependency: "direct main"
86
+    description:
87
+      name: permission_handler
88
+      url: "https://pub.flutter-io.cn"
89
+    source: hosted
90
+    version: "3.0.1"
84 91
   quiver:
85 92
     dependency: transitive
86 93
     description:

+ 1
- 0
example/pubspec.yaml 查看文件

@@ -12,6 +12,7 @@ dependencies:
12 12
   # The following adds the Cupertino Icons font to your application.
13 13
   # Use with the CupertinoIcons class for iOS style icons.
14 14
   cupertino_icons: ^0.1.2
15
+  permission_handler: ^3.0.0
15 16
 
16 17
 dev_dependencies:
17 18
   flutter_test:

+ 77
- 11
lib/mofun_video_view.dart 查看文件

@@ -3,6 +3,7 @@ import 'dart:async';
3 3
 import 'package:flutter/foundation.dart';
4 4
 import 'package:flutter/services.dart';
5 5
 import 'package:flutter/widgets.dart';
6
+import 'package:permission_handler/permission_handler.dart';
6 7
 
7 8
 typedef void MofunVideoViewCreatedCallback(MofunVideoViewController controller);
8 9
 
@@ -20,10 +21,11 @@ class MofunVideoView extends StatefulWidget {
20 21
   State<StatefulWidget> createState() {
21 22
     return _MofunVideoViewState();
22 23
   }
23
-
24 24
 }
25 25
 
26 26
 class _MofunVideoViewState extends State<MofunVideoView> {
27
+  MofunVideoViewController controller;
28
+
27 29
   @override
28 30
   Widget build(BuildContext context) {
29 31
     if (defaultTargetPlatform == TargetPlatform.android) {
@@ -31,7 +33,7 @@ class _MofunVideoViewState extends State<MofunVideoView> {
31 33
         viewType: _CHANNEL,
32 34
         onPlatformViewCreated: _onPlatformViewCreated,
33 35
       );
34
-    }else if (defaultTargetPlatform == TargetPlatform.iOS) {
36
+    } else if (defaultTargetPlatform == TargetPlatform.iOS) {
35 37
       return UiKitView(
36 38
         viewType: _CHANNEL,
37 39
         onPlatformViewCreated: _onPlatformViewCreated,
@@ -42,21 +44,85 @@ class _MofunVideoViewState extends State<MofunVideoView> {
42 44
   }
43 45
 
44 46
   void _onPlatformViewCreated(int id) {
45
-    if (widget.onMofunVideoViewCreated == null) {
46
-      return;
47
+    if (controller == null || controller.id != id) {
48
+      controller = MofunVideoViewController._(id);
49
+    }
50
+
51
+    if (widget.onMofunVideoViewCreated != null) {
52
+      widget.onMofunVideoViewCreated(controller);
47 53
     }
48
-    widget.onMofunVideoViewCreated(new MofunVideoViewController._(id));
49 54
   }
50 55
 }
51 56
 
52 57
 class MofunVideoViewController {
53
-  MofunVideoViewController._(int id)
58
+  final int id;
59
+  final MethodChannel _channel;
60
+
61
+  bool _hasPermission = false;
62
+
63
+  MofunVideoViewController._(this.id)
54 64
       : _channel = new MethodChannel('${_CHANNEL}_$id');
55 65
 
56
-  final MethodChannel _channel;
66
+  ///检查权限。返回是否权限充足
67
+  Future<bool> _checkPermission() async {
68
+    List<PermissionGroup> needCheck = List();
69
+
70
+    PermissionHandler permissionHandler = PermissionHandler();
71
+    PermissionStatus permission =
72
+        await permissionHandler.checkPermissionStatus(PermissionGroup.storage);
73
+    if (permission != PermissionStatus.granted) {
74
+      needCheck.add(PermissionGroup.storage);
75
+    }
76
+    permission = await permissionHandler
77
+        .checkPermissionStatus(PermissionGroup.microphone);
78
+    if (permission != PermissionStatus.granted) {
79
+      needCheck.add(PermissionGroup.microphone);
80
+    }
81
+
82
+    if (needCheck.isEmpty) {
83
+      return true;
84
+    }
85
+    Map<PermissionGroup, PermissionStatus> noPermissions =
86
+        await permissionHandler.requestPermissions(needCheck);
87
+    noPermissions.removeWhere((key, value) => value == PermissionStatus.granted);
88
+
89
+    return noPermissions.isEmpty;
90
+  }
57 91
 
58
-  Future<void> setText(String text) async {
59
-    assert(text != null);
60
-    return _channel.invokeMethod('setText', text);
92
+  Future<bool> checkPermission() async{
93
+    if(!_hasPermission){
94
+      return _checkPermission();
95
+    }
96
+    return true;
97
+  }
98
+
99
+  Future<void> toast(String msg) async {
100
+    return _channel.invokeMethod('toast', msg);
101
+  }
102
+
103
+  Future<void> play(String url) async {
104
+    await checkPermission();
105
+    return _channel.invokeMethod('play', url);
106
+  }
107
+
108
+  Future<void> stop() async {
109
+    await checkPermission();
110
+    return _channel.invokeMethod('stop');
61 111
   }
62
-}
112
+
113
+  Future<void> save(String output) async {
114
+    await checkPermission();
115
+    return _channel.invokeMethod('save', output);
116
+  }
117
+
118
+  Future<void> record() async {
119
+    await checkPermission();
120
+    return _channel.invokeMethod('record');
121
+  }
122
+
123
+  Future<void> playerAndResume() async {
124
+    await checkPermission();
125
+    return _channel.invokeMethod('playerAndResume');
126
+  }
127
+
128
+}

+ 7
- 0
pubspec.lock 查看文件

@@ -67,6 +67,13 @@ packages:
67 67
       url: "https://pub.flutter-io.cn"
68 68
     source: hosted
69 69
     version: "1.5.0"
70
+  permission_handler:
71
+    dependency: "direct main"
72
+    description:
73
+      name: permission_handler
74
+      url: "https://pub.flutter-io.cn"
75
+    source: hosted
76
+    version: "3.0.1"
70 77
   quiver:
71 78
     dependency: transitive
72 79
     description:

+ 1
- 0
pubspec.yaml 查看文件

@@ -10,6 +10,7 @@ environment:
10 10
 dependencies:
11 11
   flutter:
12 12
     sdk: flutter
13
+  permission_handler: '^3.0.0'
13 14
 
14 15
 dev_dependencies:
15 16
   flutter_test: