diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9b628be..3620067 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -55,10 +55,9 @@
-
-
+
+
diff --git a/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java
index 3d3032b..913e686 100644
--- a/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java
+++ b/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java
@@ -30,7 +30,8 @@ public class MainActivity extends BaseActivity {
R.id.btn_push,
R.id.btn_live,
R.id.btn_filter,
- R.id.btn_reverse
+ R.id.btn_reverse,
+ R.id.btn_preview
);
}
@@ -62,6 +63,9 @@ public class MainActivity extends BaseActivity {
case R.id.btn_reverse://视频倒播
intent.setClass(MainActivity.this, VideoReverseActivity.class);
break;
+ case R.id.btn_preview://视频拖动实时预览
+ intent.setClass(MainActivity.this, VideoPreviewActivity.class);
+ break;
default:
break;
}
diff --git a/app/src/main/java/com/frank/ffmpeg/activity/VideoPreviewActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/VideoPreviewActivity.java
new file mode 100644
index 0000000..1418a3c
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/activity/VideoPreviewActivity.java
@@ -0,0 +1,158 @@
+package com.frank.ffmpeg.activity;
+
+import android.Manifest;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.widget.SeekBar;
+
+import com.frank.ffmpeg.R;
+import com.frank.ffmpeg.hardware.HardwareDecode;
+
+import java.io.IOException;
+import androidx.appcompat.app.AppCompatActivity;
+
+/**
+ * 视频拖动实时预览
+ * Created by frank on 2019/11/16.
+ */
+
+public class VideoPreviewActivity extends AppCompatActivity implements HardwareDecode.OnDataCallback {
+
+ private final static String TAG = VideoPreviewActivity.class.getSimpleName();
+
+ private final static String ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath();
+ private static String PATH1 = ROOT_PATH + "/What.mp4";
+// private final static String PATH2 = ROOT_PATH + "/bird-1080P.mkv";
+// PATH1 = "https://www.apple.com/105/media/cn/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797" +
+// "/films/bruce/mac-bruce-tpl-cn-2018_1280x720h.mp4";
+
+ private SeekBar previewBar;
+ private HardwareDecode hardwareDecode;
+ private long duration;
+
+ private MediaPlayer mediaPlayer;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_preview);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1234);
+ }
+
+ initView();
+ setListener();
+ }
+
+ private void initView() {
+ previewBar = findViewById(R.id.preview_bar);
+
+ SurfaceView surfaceVideo = findViewById(R.id.surface_view);
+ setCallback(surfaceVideo);
+
+ SurfaceView surfaceView = findViewById(R.id.surface_preview);
+ setCallbackAndPlay(PATH1, surfaceView);
+
+// SurfaceView surfaceViewOther = findViewById(R.id.surface_view_other);
+// setCallbackAndPlay(PATH2, surfaceViewOther);
+ }
+
+ private void setCallback(SurfaceView surfaceView) {
+ mediaPlayer = new MediaPlayer();
+ mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ Log.e(TAG, "onPrepared...");
+ mediaPlayer.start();
+ }
+ });
+
+ surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ try {
+ mediaPlayer.setDataSource(PATH1);
+ mediaPlayer.setSurface(holder.getSurface());
+ mediaPlayer.prepareAsync();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+
+ }
+ });
+
+ }
+
+ private void setCallbackAndPlay(final String filePath, SurfaceView surfaceView) {
+ surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+
+ hardwareDecode = new HardwareDecode(holder.getSurface(), filePath, VideoPreviewActivity.this);
+ hardwareDecode.decode();
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+
+ }
+ });
+ }
+
+ private void setListener() {
+ previewBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (!fromUser) {
+ return;
+ }
+ previewBar.setProgress(progress);
+ if (hardwareDecode != null && progress < duration) {
+ hardwareDecode.seekTo(progress);
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ //TODO
+ if (mediaPlayer != null) {
+ Log.e(TAG, "onStop progress=" + seekBar.getProgress());
+ mediaPlayer.seekTo(seekBar.getProgress()/1000);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onData(long duration) {
+ Log.e(TAG,"duration=" + duration);
+ this.duration = duration;
+ previewBar.setMax((int) duration);
+ }
+
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.java b/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.java
new file mode 100644
index 0000000..aff80f4
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.java
@@ -0,0 +1,174 @@
+package com.frank.ffmpeg.hardware;
+
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.util.Log;
+import android.view.Surface;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * 使用MediaExtractor抽帧,MediaCodec解码,然后渲染到Surface
+ * Created by frank on 2019/11/16.
+ */
+
+public class HardwareDecode {
+
+ private final static String TAG = HardwareDecode.class.getSimpleName();
+
+ private final static long DEQUEUE_TIME = 10 * 1000;
+
+ private final static long SLEEP_TIME = 10;
+
+ private Surface mSurface;
+
+ private String mFilePath;
+
+ private VideoDecodeThread videoDecodeThread;
+
+ private OnDataCallback mCallback;
+
+ public interface OnDataCallback {
+ void onData(long duration);
+ }
+
+ public HardwareDecode(Surface surface, String filePath, OnDataCallback onDataCallback) {
+ this.mSurface = surface;
+ this.mFilePath = filePath;
+ this.mCallback = onDataCallback;
+ }
+
+ public void decode() {
+ videoDecodeThread = new VideoDecodeThread();
+ videoDecodeThread.start();
+ }
+
+ public void seekTo(long seekPosition) {
+ if (videoDecodeThread != null && !videoDecodeThread.isInterrupted()) {
+ videoDecodeThread.seekTo(seekPosition);
+ }
+ }
+
+ private class VideoDecodeThread extends Thread {
+
+ private MediaExtractor mediaExtractor;
+
+ void seekTo(long seekPosition) {
+ try {
+ if (mediaExtractor != null) {
+ mediaExtractor.seekTo(seekPosition, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ }
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "seekTo error=" + e.toString());
+ }
+ }
+
+ @Override
+ public void run() {
+ super.run();
+
+ mediaExtractor = new MediaExtractor();
+ MediaFormat mediaFormat = null;
+ String mimeType = "";
+ try {
+ mediaExtractor.setDataSource(mFilePath);
+ for (int i=0; i= 0) {
+ ByteBuffer inputBuffer = inputBuffers[inputIndex];
+ int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);
+ //入队列
+ if (sampleSize < 0) {
+ isEof = true;
+ mediaCodec.queueInputBuffer(inputIndex,0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+ } else {
+ mediaCodec.queueInputBuffer(inputIndex, 0, sampleSize, mediaExtractor.getSampleTime(), 0);
+ mediaExtractor.advance();
+ }
+ }
+
+ //出队列
+ int outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, DEQUEUE_TIME);
+ switch (outputIndex) {
+ case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
+ Log.e(TAG, "output format changed...");
+ break;
+ case MediaCodec.INFO_TRY_AGAIN_LATER:
+ Log.i(TAG, "try again later...");
+ break;
+ case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
+ Log.e(TAG, "output buffer changed...");
+ break;
+ default:
+// long time = (System.currentTimeMillis()-startTime);
+// if (time > 50) {
+// Log.e(TAG, "pts=" + bufferInfo.presentationTimeUs + "--time=" + time);
+// }
+// startTime = System.currentTimeMillis();
+// while (bufferInfo.presentationTimeUs > (System.currentTimeMillis() - startTime)*1000) {
+// try {
+// Thread.sleep(SLEEP_TIME);
+// } catch (InterruptedException e) {
+// e.printStackTrace();
+// Log.e(TAG, "error=" + e.toString());
+// }
+// }
+ //渲染到surface
+ mediaCodec.releaseOutputBuffer(outputIndex, true);
+ break;
+ }
+
+ if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ isEof = true;
+ Log.e(TAG, "is end of stream...");
+ }
+ }
+
+ mediaCodec.stop();
+ mediaCodec.release();
+ mediaExtractor.release();
+ } catch (IOException e) {
+ Log.e(TAG, "setDataSource error=" + e.toString());
+ }
+
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/util/TimeUtil.java b/app/src/main/java/com/frank/ffmpeg/util/TimeUtil.java
new file mode 100644
index 0000000..b0f9243
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/util/TimeUtil.java
@@ -0,0 +1,79 @@
+package com.frank.ffmpeg.util;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * 时间转换工具类
+ * Created by frank on 2018/11/12.
+ */
+
+public class TimeUtil {
+
+ private static final String YMDHMS= "yyyy-MM-dd HH:mm:ss";
+
+ /**
+ * 时间戳年月日时分秒
+ * @param time time
+ * @return 年月日时分秒 yyyy/MM/dd HH:mm:ss
+ */
+ public static String getDetailTime(long time) {
+ SimpleDateFormat format = new SimpleDateFormat(YMDHMS, Locale.getDefault());
+ Date date = new Date(time);
+ return format.format(date);
+ }
+
+ /**
+ * 时间转为时间戳
+ * @param time time
+ * @return 时间戳
+ */
+ public static long getLongTime(String time, Locale locale) {
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(YMDHMS, locale);
+ try {
+ Date dt = simpleDateFormat.parse(time);
+ return dt.getTime();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ return 0;
+ }
+
+ private static String addZero(int time){
+ if (time >= 0 && time < 10){
+ return "0" + time;
+ }else if(time >= 10){
+ return "" + time;
+ }else {
+ return "";
+ }
+ }
+
+ /**
+ * 获取视频时长
+ * @param time time
+ * @return 视频时长
+ */
+ public static String getVideoTime(long time){
+ if (time <= 0)
+ return null;
+ time = time / 1000;
+ int second, minute=0, hour=0;
+ second = (int)time % 60;
+ time = time / 60;
+ if (time > 0){
+ minute = (int)time % 60;
+ hour = (int)time / 60;
+ }
+ if (hour > 0){
+ return addZero(hour) + ":" + addZero(minute) + ":" + addZero(second);
+ }else if (minute > 0){
+ return addZero(minute) + ":" + addZero(second);
+ }else {
+ return "00:" + addZero(second);
+ }
+ }
+
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 9b29e52..d8af25c 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -74,4 +74,13 @@
android:layout_centerHorizontal="true"
android:layout_below="@+id/btn_filter"/>
+
+
diff --git a/app/src/main/res/layout/activity_preview.xml b/app/src/main/res/layout/activity_preview.xml
new file mode 100644
index 0000000..581d241
--- /dev/null
+++ b/app/src/main/res/layout/activity_preview.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 464cccf..3217bc7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -39,7 +39,7 @@
视频降噪
视频转图片
视频画中画
- 悬浮窗播放
+ 播放预览
切换
开始