소스 검색

remove screen record

x1ongzhu 6 년 전
부모
커밋
d5e8dba605

+ 0 - 31
android/app/src/androidTest/java/com/izouma/mobilecybergames/RecordServiceTest.java

@@ -1,31 +0,0 @@
-package com.izouma.mobilecybergames;
-
-import android.content.Context;
-import android.content.Intent;
-import android.media.projection.MediaProjectionManager;
-
-import org.junit.Rule;
-import org.junit.Test;
-
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.rule.ServiceTestRule;
-
-import static android.content.Context.MEDIA_PROJECTION_SERVICE;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-public class RecordServiceTest {
-    private static final int REQUEST_MEDIA_PROJECTION = 1;
-
-    @Rule
-    public final ServiceTestRule  serviceRule          = new ServiceTestRule();
-    @Rule
-    public       ActivityTestRule mainActivityTestRule = new ActivityTestRule<>(MainActivity.class);
-
-    @Test
-    public void testWithBoundService() {
-        Context context = getInstrumentation().getTargetContext();
-        MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) context.getSystemService(MEDIA_PROJECTION_SERVICE);
-        Intent captureIntent = mMediaProjectionManager.createScreenCaptureIntent();
-        mainActivityTestRule.getActivity().startActivityForResult(captureIntent, REQUEST_MEDIA_PROJECTION);
-    }
-}

+ 8 - 97
android/app/src/main/java/com/izouma/mobilecybergames/ScreenStreamPlugin.java

@@ -2,19 +2,16 @@ package com.izouma.mobilecybergames;
 
 import android.annotation.TargetApi;
 import android.app.Activity;
-import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
-import android.media.projection.MediaProjection;
 import android.media.projection.MediaProjectionManager;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
-import android.os.StrictMode;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -36,14 +33,6 @@ import com.alivc.live.pusher.AlivcLivePusher;
 import com.alivc.live.pusher.AlivcPreviewOrientationEnum;
 import com.alivc.live.pusher.AlivcResolutionEnum;
 
-import net.yrom.screenrecorder.Notifications;
-import net.yrom.screenrecorder.ScreenRecorder;
-import net.yrom.screenrecorder.VideoEncodeConfig;
-
-import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
 import java.util.Map;
 import java.util.regex.Pattern;
 
@@ -53,7 +42,6 @@ import io.flutter.plugin.common.PluginRegistry;
 
 import static android.content.Context.MEDIA_PROJECTION_SERVICE;
 import static android.content.Context.WINDOW_SERVICE;
-import static net.yrom.screenrecorder.ScreenRecorder.VIDEO_AVC;
 
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class ScreenStreamPlugin implements MethodChannel.MethodCallHandler, PluginRegistry.ActivityResultListener {
@@ -69,49 +57,9 @@ public class ScreenStreamPlugin implements MethodChannel.MethodCallHandler, Plug
     private String               url;
     private MethodChannel.Result result;
 
-    private MediaProjectionManager     mediaProjectionManager;
-    private ScreenRecorder             mRecorder;
-    private Notifications              mNotifications;
     private WindowManager              windowManager;
     private WindowManager.LayoutParams layoutParams;
     private Button                     floatButton;
-    private BroadcastReceiver          mStopActionReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            File file = new File(mRecorder.getSavedPath());
-            if ("com.izouma.screenrecorder.action.STOP".equals(intent.getAction())) {
-                mRecorder.quit();
-                mRecorder = null;
-                try {
-                    registrar.context().unregisterReceiver(mStopActionReceiver);
-                } catch (Exception e) {
-                    //ignored
-                }
-
-                Toast.makeText(context, "Recorder stopped!\n Saved file " + file, Toast.LENGTH_LONG).show();
-                StrictMode.VmPolicy vmPolicy = StrictMode.getVmPolicy();
-                try {
-                    // disable detecting FileUriExposure on public file
-                    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());
-                    viewResult(file);
-                } finally {
-                    StrictMode.setVmPolicy(vmPolicy);
-                }
-            }
-        }
-
-        private void viewResult(File file) {
-            Intent view = new Intent(Intent.ACTION_VIEW);
-            view.addCategory(Intent.CATEGORY_DEFAULT);
-            view.setDataAndType(Uri.fromFile(file), VIDEO_AVC);
-            view.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            try {
-                registrar.context().startActivity(view);
-            } catch (ActivityNotFoundException e) {
-                // no activity can open this video
-            }
-        }
-    };
 
     public static void registerWith(PluginRegistry.Registrar registrar) {
         final MethodChannel channel = new MethodChannel(registrar.messenger(), "screen_stream");
@@ -145,8 +93,6 @@ public class ScreenStreamPlugin implements MethodChannel.MethodCallHandler, Plug
         mAlivcLivePushConfig.setAudioProfile(AlivcAudioAACProfileEnum.AAC_LC);//设置音频编码模式
         mAlivcLivePushConfig.setEnableBitrateControl(true);// 打开码率自适应,默认为true
 
-        mediaProjectionManager = (MediaProjectionManager) registrar.context().getApplicationContext().getSystemService(MEDIA_PROJECTION_SERVICE);
-        mNotifications = new Notifications(registrar.context().getApplicationContext());
         windowManager = (WindowManager) this.registrar.context().getSystemService(WINDOW_SERVICE);
         layoutParams = new WindowManager.LayoutParams();
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -415,51 +361,16 @@ public class ScreenStreamPlugin implements MethodChannel.MethodCallHandler, Plug
         switch (requestCode) {
             case CAPTURE_PERMISSION_REQUEST_CODE: {
                 if (resultCode == Activity.RESULT_OK) {
-                    // AlivcLivePushConfig.setMediaProjectionPermissionResultData(data);
-                    // if (mAlivcLivePushConfig.getMediaProjectionPermissionResultData() != null) {
-                    //     if (mAlivcLivePusher == null) {
-                    //         startPushWithoutSurface();
-                    //         if (result != null) {
-                    //             result.success("success");
-                    //             return true;
-                    //         }
-                    //     }
-                    // }
-                    MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
-                    VideoEncodeConfig video = new VideoEncodeConfig(1920, 1080, 12000000,
-                            24, 1, "OMX.qcom.video.encoder.avc", VIDEO_AVC, null);
-                    File dir = registrar.context().getExternalFilesDir("record");
-                    SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US);
-                    File file = new File(dir, "Screen-" + format.format(new Date()) + "-" + ".mp4");
-                    mRecorder = new ScreenRecorder(video, null, 1, mediaProjection, file.getAbsolutePath());
-                    registrar.context().registerReceiver(mStopActionReceiver, new IntentFilter("com.izouma.screenrecorder.action.STOP"));
-                    mRecorder.start();
-                    mRecorder.setCallback(new ScreenRecorder.Callback() {
-                        long startTime = 0;
-
-                        @Override
-                        public void onStop(Throwable error) {
-                            registrar.activity().runOnUiThread(() -> {
-                                mNotifications.clear();
-                                mRecorder.quit();
-                                mRecorder = null;
-                            });
-                        }
-
-                        @Override
-                        public void onStart() {
-                            mNotifications.recording(0);
-                        }
-
-                        @Override
-                        public void onRecording(long presentationTimeUs) {
-                            if (startTime <= 0) {
-                                startTime = presentationTimeUs;
+                    AlivcLivePushConfig.setMediaProjectionPermissionResultData(data);
+                    if (mAlivcLivePushConfig.getMediaProjectionPermissionResultData() != null) {
+                        if (mAlivcLivePusher == null) {
+                            startPushWithoutSurface();
+                            if (result != null) {
+                                result.success("success");
+                                return true;
                             }
-                            long time = (presentationTimeUs - startTime) / 1000;
-                            mNotifications.recording(time);
                         }
-                    });
+                    }
                 }
                 if (result != null) {
                     result.error("needs permission", "permission not granted", null);

+ 0 - 64
android/app/src/main/java/net/yrom/screenrecorder/AudioEncodeConfig.java

@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2017 Yrom Wang <http://www.yrom.net>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.yrom.screenrecorder;
-
-import android.media.MediaFormat;
-
-import java.util.Objects;
-
-/**
- * @author yrom
- * @version 2017/12/3
- */
-public class AudioEncodeConfig {
-    final String codecName;
-    final String mimeType;
-    final int    bitRate;
-    final int    sampleRate;
-    final int    channelCount;
-    final int    profile;
-
-    public AudioEncodeConfig(String codecName, String mimeType,
-                             int bitRate, int sampleRate, int channelCount, int profile) {
-        this.codecName = codecName;
-        this.mimeType = Objects.requireNonNull(mimeType);
-        this.bitRate = bitRate;
-        this.sampleRate = sampleRate;
-        this.channelCount = channelCount;
-        this.profile = profile;
-    }
-
-    MediaFormat toFormat() {
-        MediaFormat format = MediaFormat.createAudioFormat(mimeType, sampleRate, channelCount);
-        format.setInteger(MediaFormat.KEY_AAC_PROFILE, profile);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
-        //format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 4096 * 4);
-        return format;
-    }
-
-    @Override
-    public String toString() {
-        return "AudioEncodeConfig{" +
-                "codecName='" + codecName + '\'' +
-                ", mimeType='" + mimeType + '\'' +
-                ", bitRate=" + bitRate +
-                ", sampleRate=" + sampleRate +
-                ", channelCount=" + channelCount +
-                ", profile=" + profile +
-                '}';
-    }
-}

+ 0 - 42
android/app/src/main/java/net/yrom/screenrecorder/AudioEncoder.java

@@ -1,42 +0,0 @@
-/*
- * Copyright (c) 2017 Yrom Wang <http://www.yrom.net>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.yrom.screenrecorder;
-
-import android.media.MediaFormat;
-import android.os.Build;
-
-import androidx.annotation.RequiresApi;
-
-/**
- * @author yrom
- * @version 2017/12/3
- */
-@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-class AudioEncoder extends BaseEncoder {
-    private final AudioEncodeConfig mConfig;
-
-    AudioEncoder(AudioEncodeConfig config) {
-        super(config.codecName);
-        this.mConfig = config;
-    }
-
-    @Override
-    protected MediaFormat createMediaFormat() {
-        return mConfig.toFormat();
-    }
-
-}

+ 0 - 216
android/app/src/main/java/net/yrom/screenrecorder/BaseEncoder.java

@@ -1,216 +0,0 @@
-/*
- * Copyright (c) 2017 Yrom Wang <http://www.yrom.net>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.yrom.screenrecorder;
-
-import android.media.MediaCodec;
-import android.media.MediaFormat;
-import android.os.Build;
-import android.os.Looper;
-import android.util.Log;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Objects;
-
-import androidx.annotation.RequiresApi;
-
-/**
- * @author yrom
- * @version 2017/12/4
- */
-@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-abstract class BaseEncoder implements Encoder {
-    static abstract class Callback implements Encoder.Callback {
-        void onInputBufferAvailable(BaseEncoder encoder, int index) {
-        }
-
-        void onOutputFormatChanged(BaseEncoder encoder, MediaFormat format) {
-        }
-
-        void onOutputBufferAvailable(BaseEncoder encoder, int index, MediaCodec.BufferInfo info) {
-        }
-    }
-
-    BaseEncoder() {
-    }
-
-    BaseEncoder(String codecName) {
-        this.mCodecName = codecName;
-    }
-
-    @Override
-    public void setCallback(Encoder.Callback callback) {
-        if (!(callback instanceof Callback)) {
-            throw new IllegalArgumentException();
-        }
-        this.setCallback((Callback) callback);
-    }
-
-    void setCallback(Callback callback) {
-        if (this.mEncoder != null) throw new IllegalStateException("mEncoder is not null");
-        this.mCallback = callback;
-    }
-
-    /**
-     * Must call in a worker handler thread!
-     */
-    @Override
-    public void prepare() throws IOException {
-        if (Looper.myLooper() == null
-                || Looper.myLooper() == Looper.getMainLooper()) {
-            throw new IllegalStateException("should run in a HandlerThread");
-        }
-        if (mEncoder != null) {
-            throw new IllegalStateException("prepared!");
-        }
-        MediaFormat format = createMediaFormat();
-        Log.d("Encoder", "Create media format: " + format);
-
-        String mimeType = format.getString(MediaFormat.KEY_MIME);
-        final MediaCodec encoder = createEncoder(mimeType);
-        try {
-            if (this.mCallback != null) {
-                // NOTE: MediaCodec maybe crash on some devices due to null callback
-                encoder.setCallback(mCodecCallback);
-            }
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            onEncoderConfigured(encoder);
-            encoder.start();
-        } catch (MediaCodec.CodecException e) {
-            Log.e("Encoder", "Configure codec failure!\n  with format" + format, e);
-            throw e;
-        }
-        mEncoder = encoder;
-    }
-
-    /**
-     * call immediately after {@link #getEncoder() MediaCodec}
-     * configure with {@link #createMediaFormat() MediaFormat} success
-     *
-     * @param encoder
-     */
-    protected void onEncoderConfigured(MediaCodec encoder) {
-    }
-
-    /**
-     * create a new instance of MediaCodec
-     */
-    private MediaCodec createEncoder(String type) throws IOException {
-        try {
-            // use codec name first
-            if (this.mCodecName != null) {
-                return MediaCodec.createByCodecName(mCodecName);
-            }
-        } catch (IOException e) {
-            Log.w("@@", "Create MediaCodec by name '" + mCodecName + "' failure!", e);
-        }
-        return MediaCodec.createEncoderByType(type);
-    }
-
-    /**
-     * create {@link MediaFormat} for {@link MediaCodec}
-     */
-    protected abstract MediaFormat createMediaFormat();
-
-    protected final MediaCodec getEncoder() {
-        return Objects.requireNonNull(mEncoder, "doesn't prepare()");
-    }
-
-    /**
-     * @throws NullPointerException if prepare() not call
-     * @see MediaCodec#getOutputBuffer(int)
-     */
-    public final ByteBuffer getOutputBuffer(int index) {
-        return getEncoder().getOutputBuffer(index);
-    }
-
-    /**
-     * @throws NullPointerException if prepare() not call
-     * @see MediaCodec#getInputBuffer(int)
-     */
-    public final ByteBuffer getInputBuffer(int index) {
-        return getEncoder().getInputBuffer(index);
-    }
-
-    /**
-     * @throws NullPointerException if prepare() not call
-     * @see MediaCodec#queueInputBuffer(int, int, int, long, int)
-     * @see MediaCodec#getInputBuffer(int)
-     */
-    public final void queueInputBuffer(int index, int offset, int size, long pstTs, int flags) {
-        getEncoder().queueInputBuffer(index, offset, size, pstTs, flags);
-    }
-
-    /**
-     * @throws NullPointerException if prepare() not call
-     * @see MediaCodec#releaseOutputBuffer(int, boolean)
-     */
-    public final void releaseOutputBuffer(int index) {
-        getEncoder().releaseOutputBuffer(index, false);
-    }
-
-    /**
-     * @see MediaCodec#stop()
-     */
-    @Override
-    public void stop() {
-        if (mEncoder != null) {
-            mEncoder.stop();
-        }
-    }
-
-    /**
-     * @see MediaCodec#release()
-     */
-    @Override
-    public void release() {
-        if (mEncoder != null) {
-            mEncoder.release();
-            mEncoder = null;
-        }
-    }
-
-    private String              mCodecName;
-    private MediaCodec          mEncoder;
-    private Callback            mCallback;
-    /**
-     * let media codec run async mode if mCallback != null
-     */
-    private MediaCodec.Callback mCodecCallback = new MediaCodec.Callback() {
-        @Override
-        public void onInputBufferAvailable(MediaCodec codec, int index) {
-            mCallback.onInputBufferAvailable(BaseEncoder.this, index);
-        }
-
-        @Override
-        public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
-            mCallback.onOutputBufferAvailable(BaseEncoder.this, index, info);
-        }
-
-        @Override
-        public void onError(MediaCodec codec, MediaCodec.CodecException e) {
-            mCallback.onError(BaseEncoder.this, e);
-        }
-
-        @Override
-        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-            mCallback.onOutputFormatChanged(BaseEncoder.this, format);
-        }
-    };
-
-
-}

+ 0 - 37
android/app/src/main/java/net/yrom/screenrecorder/Encoder.java

@@ -1,37 +0,0 @@
-/*
- * Copyright (c) 2017 Yrom Wang <http://www.yrom.net>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.yrom.screenrecorder;
-
-import java.io.IOException;
-
-/**
- * @author yrom
- * @version 2017/12/4
- */
-interface Encoder {
-    void prepare() throws IOException;
-
-    void stop();
-
-    void release();
-
-    void setCallback(Callback callback);
-
-    interface Callback {
-        void onError(Encoder encoder, Exception exception);
-    }
-}

+ 0 - 366
android/app/src/main/java/net/yrom/screenrecorder/MicRecorder.java

@@ -1,366 +0,0 @@
-/*
- * Copyright (c) 2017 Yrom Wang <http://www.yrom.net>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.yrom.screenrecorder;
-
-import android.media.AudioFormat;
-import android.media.AudioRecord;
-import android.media.MediaCodec;
-import android.media.MediaFormat;
-import android.media.MediaRecorder;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.SparseLongArray;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.LinkedList;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import androidx.annotation.RequiresApi;
-
-import static android.media.MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-import static android.media.MediaCodec.BUFFER_FLAG_KEY_FRAME;
-import static android.media.MediaCodec.INFO_OUTPUT_FORMAT_CHANGED;
-import static android.os.Build.VERSION_CODES.N;
-
-/**
- * @author yrom
- * @version 2017/12/4
- */
-@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-class MicRecorder implements Encoder {
-    private static final String  TAG     = "MicRecorder";
-    private static final boolean VERBOSE = false;
-
-    private final AudioEncoder  mEncoder;
-    private final HandlerThread mRecordThread;
-    private       RecordHandler mRecordHandler;
-    private       AudioRecord   mMic; // access in mRecordThread only!
-    private       int           mSampleRate;
-    private       int           mChannelConfig;
-    private       int           mFormat = AudioFormat.ENCODING_PCM_16BIT;
-
-    private AtomicBoolean        mForceStop = new AtomicBoolean(false);
-    private BaseEncoder.Callback mCallback;
-    private CallbackDelegate     mCallbackDelegate;
-    private int                  mChannelsSampleRate;
-
-    MicRecorder(AudioEncodeConfig config) {
-        mEncoder = new AudioEncoder(config);
-        mSampleRate = config.sampleRate;
-        mChannelsSampleRate = mSampleRate * config.channelCount;
-        if (VERBOSE) Log.i(TAG, "in bitrate " + mChannelsSampleRate * 16 /* PCM_16BIT*/);
-        mChannelConfig = config.channelCount == 2 ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
-        mRecordThread = new HandlerThread(TAG);
-    }
-
-    @Override
-    public void setCallback(Callback callback) {
-        this.mCallback = (BaseEncoder.Callback) callback;
-    }
-
-    public void setCallback(BaseEncoder.Callback callback) {
-        this.mCallback = callback;
-    }
-
-    @Override
-    public void prepare() throws IOException {
-        Looper myLooper = Objects.requireNonNull(Looper.myLooper(), "Should prepare in HandlerThread");
-        // run callback in caller thread
-        mCallbackDelegate = new CallbackDelegate(myLooper, mCallback);
-        mRecordThread.start();
-        mRecordHandler = new RecordHandler(mRecordThread.getLooper());
-        mRecordHandler.sendEmptyMessage(MSG_PREPARE);
-    }
-
-    @Override
-    public void stop() {
-        // clear callback queue
-        mCallbackDelegate.removeCallbacksAndMessages(null);
-        mForceStop.set(true);
-        if (mRecordHandler != null) mRecordHandler.sendEmptyMessage(MSG_STOP);
-    }
-
-    @Override
-    public void release() {
-        if (mRecordHandler != null) mRecordHandler.sendEmptyMessage(MSG_RELEASE);
-        mRecordThread.quitSafely();
-    }
-
-    void releaseOutputBuffer(int index) {
-        if (VERBOSE) Log.d(TAG, "audio encoder released output buffer index=" + index);
-        Message.obtain(mRecordHandler, MSG_RELEASE_OUTPUT, index, 0).sendToTarget();
-    }
-
-
-    ByteBuffer getOutputBuffer(int index) {
-        return mEncoder.getOutputBuffer(index);
-    }
-
-
-    private static class CallbackDelegate extends Handler {
-        private BaseEncoder.Callback mCallback;
-
-        CallbackDelegate(Looper l, BaseEncoder.Callback callback) {
-            super(l);
-            this.mCallback = callback;
-        }
-
-
-        void onError(Encoder encoder, Exception exception) {
-            Message.obtain(this, () -> {
-                if (mCallback != null) {
-                    mCallback.onError(encoder, exception);
-                }
-            }).sendToTarget();
-        }
-
-        void onOutputFormatChanged(BaseEncoder encoder, MediaFormat format) {
-            Message.obtain(this, () -> {
-                if (mCallback != null) {
-                    mCallback.onOutputFormatChanged(encoder, format);
-                }
-            }).sendToTarget();
-        }
-
-        void onOutputBufferAvailable(BaseEncoder encoder, int index, MediaCodec.BufferInfo info) {
-            Message.obtain(this, () -> {
-                if (mCallback != null) {
-                    mCallback.onOutputBufferAvailable(encoder, index, info);
-                }
-            }).sendToTarget();
-        }
-
-    }
-
-    private static final int MSG_PREPARE        = 0;
-    private static final int MSG_FEED_INPUT     = 1;
-    private static final int MSG_DRAIN_OUTPUT   = 2;
-    private static final int MSG_RELEASE_OUTPUT = 3;
-    private static final int MSG_STOP           = 4;
-    private static final int MSG_RELEASE        = 5;
-
-    private class RecordHandler extends Handler {
-
-        private LinkedList<MediaCodec.BufferInfo> mCachedInfos               = new LinkedList<>();
-        private LinkedList<Integer>               mMuxingOutputBufferIndices = new LinkedList<>();
-        private int                               mPollRate                  = 2048_000 / mSampleRate; // poll per 2048 samples
-
-        RecordHandler(Looper l) {
-            super(l);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_PREPARE:
-                    AudioRecord r = createAudioRecord(mSampleRate, mChannelConfig, mFormat);
-                    if (r == null) {
-                        Log.e(TAG, "create audio record failure");
-                        mCallbackDelegate.onError(MicRecorder.this, new IllegalArgumentException());
-                        break;
-                    } else {
-                        r.startRecording();
-                        mMic = r;
-                    }
-                    try {
-                        mEncoder.prepare();
-                    } catch (Exception e) {
-                        mCallbackDelegate.onError(MicRecorder.this, e);
-                        break;
-                    }
-                case MSG_FEED_INPUT:
-                    if (!mForceStop.get()) {
-                        int index = pollInput();
-                        if (VERBOSE)
-                            Log.d(TAG, "audio encoder returned input buffer index=" + index);
-                        if (index >= 0) {
-                            feedAudioEncoder(index);
-                            // tell encoder to eat the fresh meat!
-                            if (!mForceStop.get()) sendEmptyMessage(MSG_DRAIN_OUTPUT);
-                        } else {
-                            // try later...
-                            if (VERBOSE) Log.i(TAG, "try later to poll input buffer");
-                            sendEmptyMessageDelayed(MSG_FEED_INPUT, mPollRate);
-                        }
-                    }
-                    break;
-                case MSG_DRAIN_OUTPUT:
-                    offerOutput();
-                    pollInputIfNeed();
-                    break;
-                case MSG_RELEASE_OUTPUT:
-                    mEncoder.releaseOutputBuffer(msg.arg1);
-                    mMuxingOutputBufferIndices.poll(); // Nobody care what it exactly is.
-                    if (VERBOSE) Log.d(TAG, "audio encoder released output buffer index="
-                            + msg.arg1 + ", remaining=" + mMuxingOutputBufferIndices.size());
-                    pollInputIfNeed();
-                    break;
-                case MSG_STOP:
-                    if (mMic != null) {
-                        mMic.stop();
-                    }
-                    mEncoder.stop();
-                    break;
-                case MSG_RELEASE:
-                    if (mMic != null) {
-                        mMic.release();
-                        mMic = null;
-                    }
-                    mEncoder.release();
-                    break;
-            }
-        }
-
-        private void offerOutput() {
-            while (!mForceStop.get()) {
-                MediaCodec.BufferInfo info = mCachedInfos.poll();
-                if (info == null) {
-                    info = new MediaCodec.BufferInfo();
-                }
-                int index = mEncoder.getEncoder().dequeueOutputBuffer(info, 1);
-                if (VERBOSE) Log.d(TAG, "audio encoder returned output buffer index=" + index);
-                if (index == INFO_OUTPUT_FORMAT_CHANGED) {
-                    mCallbackDelegate.onOutputFormatChanged(mEncoder, mEncoder.getEncoder().getOutputFormat());
-                }
-                if (index < 0) {
-                    info.set(0, 0, 0, 0);
-                    mCachedInfos.offer(info);
-                    break;
-                }
-                mMuxingOutputBufferIndices.offer(index);
-                mCallbackDelegate.onOutputBufferAvailable(mEncoder, index, info);
-
-            }
-        }
-
-        private int pollInput() {
-            return mEncoder.getEncoder().dequeueInputBuffer(0);
-        }
-
-        private void pollInputIfNeed() {
-            if (mMuxingOutputBufferIndices.size() <= 1 && !mForceStop.get()) {
-                // need fresh data, right now!
-                removeMessages(MSG_FEED_INPUT);
-                sendEmptyMessageDelayed(MSG_FEED_INPUT, 0);
-            }
-        }
-    }
-
-    /**
-     * NOTE: Should waiting all output buffer disappear queue input buffer
-     */
-    private void feedAudioEncoder(int index) {
-        if (index < 0 || mForceStop.get()) return;
-        final AudioRecord r = Objects.requireNonNull(mMic, "maybe release");
-        final boolean eos = r.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED;
-        final ByteBuffer frame = mEncoder.getInputBuffer(index);
-        int offset = frame.position();
-        int limit = frame.limit();
-        int read = 0;
-        if (!eos) {
-            read = r.read(frame, limit);
-            if (VERBOSE) Log.d(TAG, "Read frame data size " + read + " for index "
-                    + index + " buffer : " + offset + ", " + limit);
-            if (read < 0) {
-                read = 0;
-            }
-        }
-
-        long pstTs = calculateFrameTimestamp(read << 3);
-        int flags = BUFFER_FLAG_KEY_FRAME;
-
-        if (eos) {
-            flags = BUFFER_FLAG_END_OF_STREAM;
-        }
-        // feed frame to encoder
-        if (VERBOSE) Log.d(TAG, "Feed codec index=" + index + ", presentationTimeUs="
-                + pstTs + ", flags=" + flags);
-        mEncoder.queueInputBuffer(index, offset, read, pstTs, flags);
-    }
-
-
-    private static final int             LAST_FRAME_ID  = -1;
-    private              SparseLongArray mFramesUsCache = new SparseLongArray(2);
-
-    /**
-     * Gets presentation time (us) of polled frame.
-     * 1 sample = 16 bit
-     */
-    private long calculateFrameTimestamp(int totalBits) {
-        int samples = totalBits >> 4;
-        long frameUs = mFramesUsCache.get(samples, -1);
-        if (frameUs == -1) {
-            frameUs = samples * 1000_000 / mChannelsSampleRate;
-            mFramesUsCache.put(samples, frameUs);
-        }
-        long timeUs = SystemClock.elapsedRealtimeNanos() / 1000;
-        // accounts the delay of polling the audio sample data
-        timeUs -= frameUs;
-        long currentUs;
-        long lastFrameUs = mFramesUsCache.get(LAST_FRAME_ID, -1);
-        if (lastFrameUs == -1) { // it's the first frame
-            currentUs = timeUs;
-        } else {
-            currentUs = lastFrameUs;
-        }
-        if (VERBOSE)
-            Log.i(TAG, "count samples pts: " + currentUs + ", time pts: " + timeUs + ", samples: " + samples);
-        // maybe too late to acquire sample data
-        if (timeUs - currentUs >= (frameUs << 1)) {
-            // reset
-            currentUs = timeUs;
-        }
-        mFramesUsCache.put(LAST_FRAME_ID, currentUs + frameUs);
-        return currentUs;
-    }
-
-    private static AudioRecord createAudioRecord(int sampleRateInHz, int channelConfig, int audioFormat) {
-        int minBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
-        if (minBytes <= 0) {
-            Log.e(TAG, String.format(Locale.US, "Bad arguments: getMinBufferSize(%d, %d, %d)",
-                    sampleRateInHz, channelConfig, audioFormat));
-            return null;
-        }
-        AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC,
-                sampleRateInHz,
-                channelConfig,
-                audioFormat,
-                minBytes * 2);
-
-        if (record.getState() == AudioRecord.STATE_UNINITIALIZED) {
-            Log.e(TAG, String.format(Locale.US, "Bad arguments to new AudioRecord %d, %d, %d",
-                    sampleRateInHz, channelConfig, audioFormat));
-            return null;
-        }
-        if (VERBOSE) {
-            Log.i(TAG, "created AudioRecord " + record + ", MinBufferSize= " + minBytes);
-            if (Build.VERSION.SDK_INT >= N) {
-                Log.d(TAG, " size in frame " + record.getBufferSizeInFrames());
-            }
-        }
-        return record;
-    }
-
-}

+ 0 - 118
android/app/src/main/java/net/yrom/screenrecorder/Notifications.java

@@ -1,118 +0,0 @@
-/*
- * Copyright (c) 2017 Yrom Wang <http://www.yrom.net>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.yrom.screenrecorder;
-
-import android.annotation.TargetApi;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.os.Build;
-import android.os.SystemClock;
-import android.text.format.DateUtils;
-
-import androidx.annotation.RequiresApi;
-
-import static android.os.Build.VERSION_CODES.O;
-
-/**
- * @author yrom
- * @version 2017/12/1
- */
-@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-public class Notifications extends ContextWrapper {
-    private static final int    id           = 0x1fff;
-    private static final String CHANNEL_ID   = "Recording";
-    private static final String CHANNEL_NAME = "Screen Recorder Notifications";
-
-    private long                 mLastFiredTime = 0;
-    private NotificationManager  mManager;
-    private Notification.Action  mStopAction;
-    private Notification.Builder mBuilder;
-
-    public Notifications(Context context) {
-        super(context);
-        if (Build.VERSION.SDK_INT >= O) {
-            createNotificationChannel();
-        }
-    }
-
-    public void recording(long timeMs) {
-        if (SystemClock.elapsedRealtime() - mLastFiredTime < 1000) {
-            return;
-        }
-        Notification notification = getBuilder()
-                .setContentText("Length: " + DateUtils.formatElapsedTime(timeMs / 1000))
-                .build();
-        getNotificationManager().notify(id, notification);
-        mLastFiredTime = SystemClock.elapsedRealtime();
-    }
-
-    private Notification.Builder getBuilder() {
-        if (mBuilder == null) {
-            Notification.Builder builder = new Notification.Builder(this)
-                    .setContentTitle("Recording...")
-                    .setOngoing(true)
-                    .setLocalOnly(true)
-                    .setOnlyAlertOnce(true)
-                    .addAction(stopAction())
-                    .setWhen(System.currentTimeMillis())
-                    .setSmallIcon(android.R.drawable.ic_menu_upload);
-            if (Build.VERSION.SDK_INT >= O) {
-                builder.setChannelId(CHANNEL_ID)
-                        .setUsesChronometer(true);
-            }
-            mBuilder = builder;
-        }
-        return mBuilder;
-    }
-
-    @TargetApi(O)
-    private void createNotificationChannel() {
-        NotificationChannel channel =
-                new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
-        channel.setShowBadge(false);
-        getNotificationManager().createNotificationChannel(channel);
-    }
-
-    private Notification.Action stopAction() {
-        if (mStopAction == null) {
-            Intent intent = new Intent("com.izouma.screenrecorder.action.STOP").setPackage(getPackageName());
-            PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1,
-                    intent, PendingIntent.FLAG_ONE_SHOT);
-            mStopAction = new Notification.Action(android.R.drawable.ic_media_pause, "Stop", pendingIntent);
-        }
-        return mStopAction;
-    }
-
-    public void clear() {
-        mLastFiredTime = 0;
-        mBuilder = null;
-        mStopAction = null;
-        getNotificationManager().cancelAll();
-    }
-
-    NotificationManager getNotificationManager() {
-        if (mManager == null) {
-            mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-        }
-        return mManager;
-    }
-}

+ 0 - 528
android/app/src/main/java/net/yrom/screenrecorder/ScreenRecorder.java

@@ -1,528 +0,0 @@
-/*
- * Copyright (c) 2014 Yrom Wang <http://www.yrom.net>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.yrom.screenrecorder;
-
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.media.MediaCodec;
-import android.media.MediaFormat;
-import android.media.MediaMuxer;
-import android.media.projection.MediaProjection;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.LinkedList;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import androidx.annotation.RequiresApi;
-
-import static android.media.MediaFormat.MIMETYPE_AUDIO_AAC;
-import static android.media.MediaFormat.MIMETYPE_VIDEO_AVC;
-
-/**
- * @author Yrom
- */
-@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-public class ScreenRecorder {
-    private static final String  TAG           = "ScreenRecorder";
-    private static final boolean VERBOSE       = false;
-    private static final int     INVALID_INDEX = -1;
-    public static final  String  VIDEO_AVC     = MIMETYPE_VIDEO_AVC; // H.264 Advanced Video Coding
-    public static final  String  AUDIO_AAC     = MIMETYPE_AUDIO_AAC; // H.264 Advanced Audio Coding
-
-    private int             mWidth;
-    private int             mHeight;
-    private int             mDpi;
-    private String          mDstPath;
-    private MediaProjection mMediaProjection;
-    private VideoEncoder    mVideoEncoder;
-    private MicRecorder     mAudioEncoder;
-
-    private MediaFormat mVideoOutputFormat = null, mAudioOutputFormat = null;
-    private int mVideoTrackIndex = INVALID_INDEX, mAudioTrackIndex = INVALID_INDEX;
-    private MediaMuxer mMuxer;
-    private boolean    mMuxerStarted = false;
-
-    private AtomicBoolean            mForceQuit          = new AtomicBoolean(false);
-    private AtomicBoolean            mIsRunning          = new AtomicBoolean(false);
-    private VirtualDisplay           mVirtualDisplay;
-    private MediaProjection.Callback mProjectionCallback = new MediaProjection.Callback() {
-        @Override
-        public void onStop() {
-            quit();
-        }
-    };
-
-    private HandlerThread   mWorker;
-    private CallbackHandler mHandler;
-
-    private Callback                          mCallback;
-    private LinkedList<Integer>               mPendingVideoEncoderBufferIndices = new LinkedList<>();
-    private LinkedList<Integer>               mPendingAudioEncoderBufferIndices = new LinkedList<>();
-    private LinkedList<MediaCodec.BufferInfo> mPendingAudioEncoderBufferInfos   = new LinkedList<>();
-    private LinkedList<MediaCodec.BufferInfo> mPendingVideoEncoderBufferInfos   = new LinkedList<>();
-
-    boolean flag = false;
-
-    /**
-     * @param dpi for {@link VirtualDisplay}
-     */
-    public ScreenRecorder(VideoEncodeConfig video,
-                          AudioEncodeConfig audio,
-                          int dpi, MediaProjection mp,
-                          String dstPath) {
-        mWidth = video.width;
-        mHeight = video.height;
-        mDpi = dpi;
-        mMediaProjection = mp;
-        mDstPath = dstPath;
-        mVideoEncoder = new VideoEncoder(video);
-        mAudioEncoder = audio == null ? null : new MicRecorder(audio);
-
-    }
-
-    /**
-     * stop task
-     */
-    public final void quit() {
-        mForceQuit.set(true);
-        if (!mIsRunning.get()) {
-            release();
-        } else {
-            signalStop(false);
-        }
-
-    }
-
-    public void start() {
-        if (mWorker != null) throw new IllegalStateException();
-        mWorker = new HandlerThread(TAG);
-        mWorker.start();
-        mHandler = new CallbackHandler(mWorker.getLooper());
-        mHandler.sendEmptyMessage(MSG_START);
-    }
-
-    public void setCallback(Callback callback) {
-        mCallback = callback;
-    }
-
-    public String getSavedPath() {
-        return mDstPath;
-    }
-
-    public interface Callback {
-        void onStop(Throwable error);
-
-        void onStart();
-
-        void onRecording(long presentationTimeUs);
-    }
-
-    private static final int MSG_START     = 0;
-    private static final int MSG_STOP      = 1;
-    private static final int MSG_ERROR     = 2;
-    private static final int STOP_WITH_EOS = 1;
-
-    private class CallbackHandler extends Handler {
-        CallbackHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_START:
-                    try {
-                        record();
-                        if (mCallback != null) {
-                            mCallback.onStart();
-                        }
-                        break;
-                    } catch (Exception e) {
-                        msg.obj = e;
-                    }
-                case MSG_STOP:
-                case MSG_ERROR:
-                    stopEncoders();
-                    if (msg.arg1 != STOP_WITH_EOS) signalEndOfStream();
-                    if (mCallback != null) {
-                        mCallback.onStop((Throwable) msg.obj);
-                    }
-                    release();
-                    break;
-            }
-        }
-    }
-
-    private void signalEndOfStream() {
-        MediaCodec.BufferInfo eos = new MediaCodec.BufferInfo();
-        ByteBuffer buffer = ByteBuffer.allocate(0);
-        eos.set(0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-        if (VERBOSE) Log.i(TAG, "Signal EOS to muxer ");
-        if (mVideoTrackIndex != INVALID_INDEX) {
-            writeSampleData(mVideoTrackIndex, eos, buffer);
-        }
-        if (mAudioTrackIndex != INVALID_INDEX) {
-            writeSampleData(mAudioTrackIndex, eos, buffer);
-        }
-        mVideoTrackIndex = INVALID_INDEX;
-        mAudioTrackIndex = INVALID_INDEX;
-    }
-
-    private void record() {
-        if (mIsRunning.get() || mForceQuit.get()) {
-            throw new IllegalStateException();
-        }
-        if (mMediaProjection == null) {
-            throw new IllegalStateException("maybe release");
-        }
-        mIsRunning.set(true);
-
-        mMediaProjection.registerCallback(mProjectionCallback, mHandler);
-        try {
-            // create muxer
-            mMuxer = new MediaMuxer(mDstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-            // create encoder and input surface
-            prepareVideoEncoder();
-            prepareAudioEncoder();
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-
-        mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display",
-                mWidth, mHeight, mDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
-                mVideoEncoder.getInputSurface(), null, null);
-        if (VERBOSE) Log.d(TAG, "created virtual display: " + mVirtualDisplay.getDisplay());
-    }
-
-    private void muxVideo(int index, MediaCodec.BufferInfo buffer) {
-        if (!mIsRunning.get()) {
-            Log.w(TAG, "muxVideo: Already stopped!");
-            return;
-        }
-        if (!mMuxerStarted || mVideoTrackIndex == INVALID_INDEX) {
-            mPendingVideoEncoderBufferIndices.add(index);
-            mPendingVideoEncoderBufferInfos.add(buffer);
-            return;
-        }
-        ByteBuffer encodedData = mVideoEncoder.getOutputBuffer(index);
-        writeSampleData(mVideoTrackIndex, buffer, encodedData);
-        mVideoEncoder.releaseOutputBuffer(index);
-        if ((buffer.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-            if (VERBOSE)
-                Log.d(TAG, "Stop encoder and muxer, since the buffer has been marked with EOS");
-            // send release msg
-            mVideoTrackIndex = INVALID_INDEX;
-            signalStop(true);
-        }
-
-        // if (!flag) {
-        //     byte[] byeArray = encodedData.array();
-        //     Bitmap bitmap = BitmapFactory.decodeByteArray(byeArray, 0, byeArray.length);
-        //     File file = new File(Environment.getExternalStorageDirectory(), "111.jpg");
-        //     try (FileOutputStream out = new FileOutputStream(file)) {
-        //         bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
-        //     } catch (IOException e) {
-        //         e.printStackTrace();
-        //     }
-        //     flag = true;
-        // }
-    }
-
-
-    private void muxAudio(int index, MediaCodec.BufferInfo buffer) {
-        if (!mIsRunning.get()) {
-            Log.w(TAG, "muxAudio: Already stopped!");
-            return;
-        }
-        if (!mMuxerStarted || mAudioTrackIndex == INVALID_INDEX) {
-            mPendingAudioEncoderBufferIndices.add(index);
-            mPendingAudioEncoderBufferInfos.add(buffer);
-            return;
-
-        }
-        ByteBuffer encodedData = mAudioEncoder.getOutputBuffer(index);
-        writeSampleData(mAudioTrackIndex, buffer, encodedData);
-        mAudioEncoder.releaseOutputBuffer(index);
-        if ((buffer.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-            if (VERBOSE)
-                Log.d(TAG, "Stop encoder and muxer, since the buffer has been marked with EOS");
-            mAudioTrackIndex = INVALID_INDEX;
-            signalStop(true);
-        }
-    }
-
-    private void writeSampleData(int track, MediaCodec.BufferInfo buffer, ByteBuffer encodedData) {
-        if ((buffer.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
-            // The codec config data was pulled out and fed to the muxer when we got
-            // the INFO_OUTPUT_FORMAT_CHANGED status.
-            // Ignore it.
-            if (VERBOSE) Log.d(TAG, "Ignoring BUFFER_FLAG_CODEC_CONFIG");
-            buffer.size = 0;
-        }
-        boolean eos = (buffer.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-        if (buffer.size == 0 && !eos) {
-            if (VERBOSE) Log.d(TAG, "info.size == 0, drop it.");
-            encodedData = null;
-        } else {
-            if (buffer.presentationTimeUs != 0) { // maybe 0 if eos
-                if (track == mVideoTrackIndex) {
-                    resetVideoPts(buffer);
-                } else if (track == mAudioTrackIndex) {
-                    resetAudioPts(buffer);
-                }
-            }
-            if (VERBOSE)
-                Log.d(TAG, "[" + Thread.currentThread().getId() + "] Got buffer, track=" + track
-                        + ", info: size=" + buffer.size
-                        + ", presentationTimeUs=" + buffer.presentationTimeUs);
-            if (!eos && mCallback != null) {
-                mCallback.onRecording(buffer.presentationTimeUs);
-            }
-        }
-        if (encodedData != null) {
-            encodedData.position(buffer.offset);
-            encodedData.limit(buffer.offset + buffer.size);
-            mMuxer.writeSampleData(track, encodedData, buffer);
-            if (VERBOSE)
-                Log.i(TAG, "Sent " + buffer.size + " bytes to MediaMuxer on track " + track);
-        }
-    }
-
-    private long mVideoPtsOffset, mAudioPtsOffset;
-
-    private void resetAudioPts(MediaCodec.BufferInfo buffer) {
-        if (mAudioPtsOffset == 0) {
-            mAudioPtsOffset = buffer.presentationTimeUs;
-            buffer.presentationTimeUs = 0;
-        } else {
-            buffer.presentationTimeUs -= mAudioPtsOffset;
-        }
-    }
-
-    private void resetVideoPts(MediaCodec.BufferInfo buffer) {
-        if (mVideoPtsOffset == 0) {
-            mVideoPtsOffset = buffer.presentationTimeUs;
-            buffer.presentationTimeUs = 0;
-        } else {
-            buffer.presentationTimeUs -= mVideoPtsOffset;
-        }
-    }
-
-    private void resetVideoOutputFormat(MediaFormat newFormat) {
-        // should happen before receiving buffers, and should only happen once
-        if (mVideoTrackIndex >= 0 || mMuxerStarted) {
-            throw new IllegalStateException("output format already changed!");
-        }
-        if (VERBOSE)
-            Log.i(TAG, "Video output format changed.\n New format: " + newFormat.toString());
-        mVideoOutputFormat = newFormat;
-    }
-
-    private void resetAudioOutputFormat(MediaFormat newFormat) {
-        // should happen before receiving buffers, and should only happen once
-        if (mAudioTrackIndex >= 0 || mMuxerStarted) {
-            throw new IllegalStateException("output format already changed!");
-        }
-        if (VERBOSE)
-            Log.i(TAG, "Audio output format changed.\n New format: " + newFormat.toString());
-        mAudioOutputFormat = newFormat;
-    }
-
-    private void startMuxerIfReady() {
-        if (mMuxerStarted || mVideoOutputFormat == null
-                || (mAudioEncoder != null && mAudioOutputFormat == null)) {
-            return;
-        }
-
-        mVideoTrackIndex = mMuxer.addTrack(mVideoOutputFormat);
-        mAudioTrackIndex = mAudioEncoder == null ? INVALID_INDEX : mMuxer.addTrack(mAudioOutputFormat);
-        mMuxer.start();
-        mMuxerStarted = true;
-        if (VERBOSE) Log.i(TAG, "Started media muxer, videoIndex=" + mVideoTrackIndex);
-        if (mPendingVideoEncoderBufferIndices.isEmpty() && mPendingAudioEncoderBufferIndices.isEmpty()) {
-            return;
-        }
-        if (VERBOSE) Log.i(TAG, "Mux pending video output buffers...");
-        MediaCodec.BufferInfo info;
-        while ((info = mPendingVideoEncoderBufferInfos.poll()) != null) {
-            int index = mPendingVideoEncoderBufferIndices.poll();
-            muxVideo(index, info);
-        }
-        if (mAudioEncoder != null) {
-            while ((info = mPendingAudioEncoderBufferInfos.poll()) != null) {
-                int index = mPendingAudioEncoderBufferIndices.poll();
-                muxAudio(index, info);
-            }
-        }
-        if (VERBOSE) Log.i(TAG, "Mux pending video output buffers done.");
-    }
-
-    // @WorkerThread
-    private void prepareVideoEncoder() throws IOException {
-        VideoEncoder.Callback callback = new VideoEncoder.Callback() {
-            boolean ranIntoError = false;
-
-            @Override
-            public void onOutputBufferAvailable(BaseEncoder codec, int index, MediaCodec.BufferInfo info) {
-                if (VERBOSE) Log.i(TAG, "VideoEncoder output buffer available: index=" + index);
-                try {
-                    muxVideo(index, info);
-                } catch (Exception e) {
-                    Log.e(TAG, "Muxer encountered an error! ", e);
-                    Message.obtain(mHandler, MSG_ERROR, e).sendToTarget();
-                }
-            }
-
-            @Override
-            public void onError(Encoder codec, Exception e) {
-                ranIntoError = true;
-                Log.e(TAG, "VideoEncoder ran into an error! ", e);
-                Message.obtain(mHandler, MSG_ERROR, e).sendToTarget();
-            }
-
-            @Override
-            public void onOutputFormatChanged(BaseEncoder codec, MediaFormat format) {
-                resetVideoOutputFormat(format);
-                startMuxerIfReady();
-            }
-        };
-        mVideoEncoder.setCallback(callback);
-        mVideoEncoder.prepare();
-    }
-
-    private void prepareAudioEncoder() throws IOException {
-        final MicRecorder micRecorder = mAudioEncoder;
-        if (micRecorder == null) return;
-        AudioEncoder.Callback callback = new AudioEncoder.Callback() {
-            boolean ranIntoError = false;
-
-            @Override
-            public void onOutputBufferAvailable(BaseEncoder codec, int index, MediaCodec.BufferInfo info) {
-                if (VERBOSE)
-                    Log.i(TAG, "[" + Thread.currentThread().getId() + "] AudioEncoder output buffer available: index=" + index);
-                try {
-                    muxAudio(index, info);
-                } catch (Exception e) {
-                    Log.e(TAG, "Muxer encountered an error! ", e);
-                    Message.obtain(mHandler, MSG_ERROR, e).sendToTarget();
-                }
-            }
-
-            @Override
-            public void onOutputFormatChanged(BaseEncoder codec, MediaFormat format) {
-                if (VERBOSE)
-                    Log.d(TAG, "[" + Thread.currentThread().getId() + "] AudioEncoder returned new format " + format);
-                resetAudioOutputFormat(format);
-                startMuxerIfReady();
-            }
-
-            @Override
-            public void onError(Encoder codec, Exception e) {
-                ranIntoError = true;
-                Log.e(TAG, "MicRecorder ran into an error! ", e);
-                Message.obtain(mHandler, MSG_ERROR, e).sendToTarget();
-            }
-
-
-        };
-        micRecorder.setCallback(callback);
-        micRecorder.prepare();
-    }
-
-    private void signalStop(boolean stopWithEOS) {
-        Message msg = Message.obtain(mHandler, MSG_STOP, stopWithEOS ? STOP_WITH_EOS : 0, 0);
-        mHandler.sendMessageAtFrontOfQueue(msg);
-    }
-
-    private void stopEncoders() {
-        mIsRunning.set(false);
-        mPendingAudioEncoderBufferInfos.clear();
-        mPendingAudioEncoderBufferIndices.clear();
-        mPendingVideoEncoderBufferInfos.clear();
-        mPendingVideoEncoderBufferIndices.clear();
-        // maybe called on an error has been occurred
-        try {
-            if (mVideoEncoder != null) mVideoEncoder.stop();
-        } catch (IllegalStateException e) {
-            // ignored
-        }
-        try {
-            if (mAudioEncoder != null) mAudioEncoder.stop();
-        } catch (IllegalStateException e) {
-            // ignored
-        }
-
-    }
-
-    private void release() {
-        if (mMediaProjection != null) {
-            mMediaProjection.unregisterCallback(mProjectionCallback);
-        }
-        if (mVirtualDisplay != null) {
-            mVirtualDisplay.release();
-            mVirtualDisplay = null;
-        }
-
-        mVideoOutputFormat = mAudioOutputFormat = null;
-        mVideoTrackIndex = mAudioTrackIndex = INVALID_INDEX;
-        mMuxerStarted = false;
-
-        if (mWorker != null) {
-            mWorker.quitSafely();
-            mWorker = null;
-        }
-        if (mVideoEncoder != null) {
-            mVideoEncoder.release();
-            mVideoEncoder = null;
-        }
-        if (mAudioEncoder != null) {
-            mAudioEncoder.release();
-            mAudioEncoder = null;
-        }
-
-        if (mMediaProjection != null) {
-            mMediaProjection.stop();
-            mMediaProjection = null;
-        }
-        if (mMuxer != null) {
-            try {
-                mMuxer.stop();
-                mMuxer.release();
-            } catch (Exception e) {
-                // ignored
-            }
-            mMuxer = null;
-        }
-        mHandler = null;
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        if (mMediaProjection != null) {
-            Log.e(TAG, "release() not called!");
-            release();
-        }
-    }
-
-}

+ 0 - 250
android/app/src/main/java/net/yrom/screenrecorder/Utils.java

@@ -1,250 +0,0 @@
-/*
- * Copyright (c) 2017 Yrom Wang <http://www.yrom.net>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.yrom.screenrecorder;
-
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.util.SparseArray;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-class Utils {
-
-
-    interface Callback {
-        void onResult(MediaCodecInfo[] infos);
-    }
-
-    static final class EncoderFinder extends AsyncTask<String, Void, MediaCodecInfo[]> {
-        private Callback func;
-
-        EncoderFinder(Callback func) {
-            this.func = func;
-        }
-
-        @Override
-        protected MediaCodecInfo[] doInBackground(String... mimeTypes) {
-            return findEncodersByType(mimeTypes[0]);
-        }
-
-        @Override
-        protected void onPostExecute(MediaCodecInfo[] mediaCodecInfos) {
-            func.onResult(mediaCodecInfos);
-        }
-    }
-
-    static void findEncodersByTypeAsync(String mimeType, Callback callback) {
-        new EncoderFinder(callback).execute(mimeType);
-    }
-
-    /**
-     * Find an encoder supported specified MIME type
-     *
-     * @return Returns empty array if not found any encoder supported specified MIME type
-     */
-    static MediaCodecInfo[] findEncodersByType(String mimeType) {
-        MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        List<MediaCodecInfo> infos = new ArrayList<>();
-        for (MediaCodecInfo info : codecList.getCodecInfos()) {
-            if (!info.isEncoder()) {
-                continue;
-            }
-            try {
-                MediaCodecInfo.CodecCapabilities cap = info.getCapabilitiesForType(mimeType);
-                if (cap == null) continue;
-            } catch (IllegalArgumentException e) {
-                // unsupported
-                continue;
-            }
-            infos.add(info);
-        }
-
-        return infos.toArray(new MediaCodecInfo[infos.size()]);
-    }
-
-
-    static SparseArray<String> sAACProfiles = new SparseArray<>();
-    static SparseArray<String> sAVCProfiles = new SparseArray<>();
-    static SparseArray<String> sAVCLevels   = new SparseArray<>();
-
-
-    /**
-     * @param avcProfileLevel AVC CodecProfileLevel
-     */
-    static String avcProfileLevelToString(MediaCodecInfo.CodecProfileLevel avcProfileLevel) {
-        if (sAVCProfiles.size() == 0 || sAVCLevels.size() == 0) {
-            initProfileLevels();
-        }
-        String profile = null, level = null;
-        int i = sAVCProfiles.indexOfKey(avcProfileLevel.profile);
-        if (i >= 0) {
-            profile = sAVCProfiles.valueAt(i);
-        }
-
-        i = sAVCLevels.indexOfKey(avcProfileLevel.level);
-        if (i >= 0) {
-            level = sAVCLevels.valueAt(i);
-        }
-
-        if (profile == null) {
-            profile = String.valueOf(avcProfileLevel.profile);
-        }
-        if (level == null) {
-            level = String.valueOf(avcProfileLevel.level);
-        }
-        return profile + '-' + level;
-    }
-
-    static String[] aacProfiles() {
-        if (sAACProfiles.size() == 0) {
-            initProfileLevels();
-        }
-        String[] profiles = new String[sAACProfiles.size()];
-        for (int i = 0; i < sAACProfiles.size(); i++) {
-            profiles[i] = sAACProfiles.valueAt(i);
-        }
-        return profiles;
-    }
-
-    static MediaCodecInfo.CodecProfileLevel toProfileLevel(String str) {
-        if (sAVCProfiles.size() == 0 || sAVCLevels.size() == 0 || sAACProfiles.size() == 0) {
-            initProfileLevels();
-        }
-        String profile = str;
-        String level = null;
-        int i = str.indexOf('-');
-        if (i > 0) { // AVC profile has level
-            profile = str.substring(0, i);
-            level = str.substring(i + 1);
-        }
-
-        MediaCodecInfo.CodecProfileLevel res = new MediaCodecInfo.CodecProfileLevel();
-        if (profile.startsWith("AVC")) {
-            res.profile = keyOfValue(sAVCProfiles, profile);
-        } else if (profile.startsWith("AAC")) {
-            res.profile = keyOfValue(sAACProfiles, profile);
-        } else {
-            try {
-                res.profile = Integer.parseInt(profile);
-            } catch (NumberFormatException e) {
-                return null;
-            }
-        }
-
-        if (level != null) {
-            if (level.startsWith("AVC")) {
-                res.level = keyOfValue(sAVCLevels, level);
-            } else {
-                try {
-                    res.level = Integer.parseInt(level);
-                } catch (NumberFormatException e) {
-                    return null;
-                }
-            }
-        }
-
-        return res.profile > 0 && res.level >= 0 ? res : null;
-    }
-
-    private static <T> int keyOfValue(SparseArray<T> array, T value) {
-        int size = array.size();
-        for (int i = 0; i < size; i++) {
-            T t = array.valueAt(i);
-            if (t == value || t.equals(value)) {
-                return array.keyAt(i);
-            }
-        }
-        return -1;
-    }
-
-    private static void initProfileLevels() {
-        Field[] fields = MediaCodecInfo.CodecProfileLevel.class.getFields();
-        for (Field f : fields) {
-            if ((f.getModifiers() & (Modifier.STATIC | Modifier.FINAL)) == 0) {
-                continue;
-            }
-            String name = f.getName();
-            SparseArray<String> target;
-            if (name.startsWith("AVCProfile")) {
-                target = sAVCProfiles;
-            } else if (name.startsWith("AVCLevel")) {
-                target = sAVCLevels;
-            } else if (name.startsWith("AACObject")) {
-                target = sAACProfiles;
-            } else {
-                continue;
-            }
-            try {
-                target.put(f.getInt(null), name);
-            } catch (IllegalAccessException e) {
-                //ignored
-            }
-        }
-    }
-
-
-    static SparseArray<String> sColorFormats = new SparseArray<>();
-
-    static String toHumanReadable(int colorFormat) {
-        if (sColorFormats.size() == 0) {
-            initColorFormatFields();
-        }
-        int i = sColorFormats.indexOfKey(colorFormat);
-        if (i >= 0) return sColorFormats.valueAt(i);
-        return "0x" + Integer.toHexString(colorFormat);
-    }
-
-    static int toColorFormat(String str) {
-        if (sColorFormats.size() == 0) {
-            initColorFormatFields();
-        }
-        int color = keyOfValue(sColorFormats, str);
-        if (color > 0) return color;
-        if (str.startsWith("0x")) {
-            return Integer.parseInt(str.substring(2), 16);
-        }
-        return 0;
-    }
-
-    private static void initColorFormatFields() {
-        // COLOR_
-        Field[] fields = MediaCodecInfo.CodecCapabilities.class.getFields();
-        for (Field f : fields) {
-            if ((f.getModifiers() & (Modifier.STATIC | Modifier.FINAL)) == 0) {
-                continue;
-            }
-            String name = f.getName();
-            if (name.startsWith("COLOR_")) {
-                try {
-                    int value = f.getInt(null);
-                    sColorFormats.put(value, name);
-                } catch (IllegalAccessException e) {
-                    // ignored
-                }
-            }
-        }
-
-    }
-}

+ 0 - 90
android/app/src/main/java/net/yrom/screenrecorder/VideoEncodeConfig.java

@@ -1,90 +0,0 @@
-/*
- * Copyright (c) 2017 Yrom Wang <http://www.yrom.net>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.yrom.screenrecorder;
-
-import android.media.MediaCodecInfo;
-import android.media.MediaFormat;
-import android.os.Build;
-
-import java.util.Objects;
-
-import androidx.annotation.RequiresApi;
-
-/**
- * @author yrom
- * @version 2017/12/3
- */
-@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-public class VideoEncodeConfig {
-    final int    width;
-    final int    height;
-    final int    bitrate;
-    final int    framerate;
-    final int    iframeInterval;
-    final String codecName;
-    final String mimeType;
-
-    final MediaCodecInfo.CodecProfileLevel codecProfileLevel;
-
-    /**
-     * @param codecName         selected codec name, maybe null
-     * @param mimeType          video MIME type, cannot be null
-     * @param codecProfileLevel profile level for video encoder nullable
-     */
-    public VideoEncodeConfig(int width, int height, int bitrate,
-                             int framerate, int iframeInterval,
-                             String codecName, String mimeType,
-                             MediaCodecInfo.CodecProfileLevel codecProfileLevel) {
-        this.width = width;
-        this.height = height;
-        this.bitrate = bitrate;
-        this.framerate = framerate;
-        this.iframeInterval = iframeInterval;
-        this.codecName = codecName;
-        this.mimeType = Objects.requireNonNull(mimeType);
-        this.codecProfileLevel = codecProfileLevel;
-    }
-
-    MediaFormat toFormat() {
-        MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height);
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
-        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iframeInterval);
-        if (codecProfileLevel != null && codecProfileLevel.profile != 0 && codecProfileLevel.level != 0) {
-            format.setInteger(MediaFormat.KEY_PROFILE, codecProfileLevel.profile);
-            format.setInteger("level", codecProfileLevel.level);
-        }
-        // maybe useful
-        // format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 10_000_000);
-        return format;
-    }
-
-    @Override
-    public String toString() {
-        return "VideoEncodeConfig{" +
-                "width=" + width +
-                ", height=" + height +
-                ", bitrate=" + bitrate +
-                ", framerate=" + framerate +
-                ", iframeInterval=" + iframeInterval +
-                ", codecName='" + codecName + '\'' +
-                ", mimeType='" + mimeType + '\'' +
-                ", codecProfileLevel=" + (codecProfileLevel == null ? "" : Utils.avcProfileLevelToString(codecProfileLevel)) +
-                '}';
-    }
-}

+ 0 - 74
android/app/src/main/java/net/yrom/screenrecorder/VideoEncoder.java

@@ -1,74 +0,0 @@
-/*
- * Copyright (c) 2017 Yrom Wang <http://www.yrom.net>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.yrom.screenrecorder;
-
-import android.media.MediaCodec;
-import android.media.MediaFormat;
-import android.os.Build;
-import android.util.Log;
-import android.view.Surface;
-
-import java.util.Objects;
-
-import androidx.annotation.RequiresApi;
-
-/**
- * @author yrom
- * @version 2017/12/3
- */
-@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-class VideoEncoder extends BaseEncoder {
-    private static final boolean VERBOSE = false;
-
-    private VideoEncodeConfig mConfig;
-    private Surface           mSurface;
-
-
-    VideoEncoder(VideoEncodeConfig config) {
-        super(config.codecName);
-        this.mConfig = config;
-    }
-
-    @Override
-    protected void onEncoderConfigured(MediaCodec encoder) {
-        mSurface = encoder.createInputSurface();
-        if (VERBOSE) Log.i("@@", "VideoEncoder create input surface: " + mSurface);
-    }
-
-    @Override
-    protected MediaFormat createMediaFormat() {
-        return mConfig.toFormat();
-    }
-
-    /**
-     * @throws NullPointerException if prepare() not call
-     */
-    Surface getInputSurface() {
-        return Objects.requireNonNull(mSurface, "doesn't prepare()");
-    }
-
-    @Override
-    public void release() {
-        if (mSurface != null) {
-            mSurface.release();
-            mSurface = null;
-        }
-        super.release();
-    }
-
-
-}