您现在的位置是:首页 >技术教程 >Android播放器拖动进度条的小图预览网站首页技术教程

Android播放器拖动进度条的小图预览

tracydragonlxy 2024-07-13 18:01:02
简介Android播放器拖动进度条的小图预览

背景

我们在使用一些播放器时,拖动进度条会有一个预览框,上一篇博客Android SeekBar控制视频播放进度(一)实现了拖动进度条调节播放进度的功能,今天我们继续完善上一篇博客的功能,增加小图预览功能。效果图如下:

效果图

在这里插入图片描述

关键代码

1. 获取指定位置的视频帧

MediaMetadataRetriever是Android原生提供的获取音视频文件信息的一个类,我们可以通过这个类的相关方法获取一些基本信息,如视频时长、宽高、帧率、方向、某一帧的图片等。

进入界面时我们开启一个子线程,以1秒的时间间隔提前把视频中的图片截取好。拖动滑动条时,只需根据当前的位置,找到最近的已经提前截取好的某帧图片即可。

为了减少内存占用,可以把截取的预览图的分辨率设置的小一些。

// 指定视频源
mmr = new MediaMetadataRetriever();
mmr.setDataSource(this, Uri.parse("android.resource://"
        + getApplicationContext().getPackageName() + "/" + R.raw.vid_bigbuckbunny));


mVideoView.setVideoURI(Uri.parse("android.resource://"
        + getApplicationContext().getPackageName() + "/" + R.raw.vid_bigbuckbunny));

mVideoView.requestFocus();

// 视频加载完成回调函数
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
    @Override
    public void onPrepared(MediaPlayer mp) {
        mp.setLooping(true);

		// 获取视频的长度
        MAX_PROGRESS = mVideoView.getDuration();
        mSeekBar.setMax((int) MAX_PROGRESS);

		// 计算按照1s截取预览图,一共有多少张预览图
        bitmaps = new Bitmap[(int) (MAX_PROGRESS / 1000) + 1];
        Log.i(TAG, "onCreate: " + MAX_PROGRESS + "," + curProgress + "," + bitmaps.length);
        // 提前截取预览图,1s的时间间隔截取,用于快进时显示
        getPreviewImage();

        // 开始线程,更新进度条的进度
        handler.postDelayed(runnable, 0);
        mVideoView.start();
    }
});

private void getPreviewImage() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < bitmaps.length - 1; i++) {
                if (refreshFlag) {
                    // 获取当前快进帧图像的bitmap对象 单位是微秒
                    // 压缩图片,减少内存占用
                    bitmaps[i] = Bitmap.createScaledBitmap(
                            mmr.getFrameAtTime(i * 1000 * 1000L,
                                    MediaMetadataRetriever.OPTION_PREVIOUS_SYNC),
                            PREVIEW_IMG_WIDTH,
                            PREVIEW_IMG_HEIGHT,
                            true);
                    Log.i(TAG, "run: " + bitmaps[i].getByteCount());
                }
            }
        }
    }).start();

}

2. 预览图的显示和隐藏

监听SeekBar的回调函数onProgressChangedonStartTrackingTouchonStopTrackingTouch。触摸函数onStartTrackingTouch触发时暂停视频播放,小窗预览图显示,onProgressChanged调节进度过程中,不停的更新预览图信息,停止调节进度onStopTrackingTouch时继续从当前位置播放视频,同时隐藏小窗预览图。

mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        Log.i(TAG, "onProgressChanged: " + progress);
        if (isSeekBarProgress) {
            updateProgress(progress);
        }
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        isSeekBarProgress = true;
        Log.i(TAG, "onStartTrackingTouch: " + mVideoView.isPlaying());
        if (mVideoView.isPlaying()) {
            mVideoView.pause();
            updatePreviewStatus(true);
        }
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        Log.i(TAG, "onStopTrackingTouch: ");
        int pro = seekBar.getProgress();
        mVideoView.seekTo(pro);
        if (!mVideoView.isPlaying()) {
            mVideoView.seekTo(pro);
            mVideoView.start();
            updatePreviewStatus(true);
        }
        isSeekBarProgress = false;
    }
});

private void updateProgress(int pro) {
    curProgress = pro;

    if (curProgress >= MAX_PROGRESS) {
        curProgress = MAX_PROGRESS - 10;
    } else if (curProgress < 0) {
        curProgress = 0;
    }

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            mCardView.setVisibility(View.VISIBLE);
            mImageViewPreview.setImageBitmap(bitmaps[(int) (curProgress / 1000)]);
        }
    });

}

private void updatePreviewStatus(final boolean b) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            mCardView.setVisibility(b ? View.VISIBLE : View.INVISIBLE);
        }
    });
}

完整代码

1. xml布局文件activity_video.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000"
    android:keepScreenOn="true"
    tools:context=".activitys.VideoActivity">

    <VideoView
        android:id="@+id/video_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <SeekBar
        android:id="@+id/seekbar"
        android:layout_width="500dp"
        android:layout_height="25dp"
        android:background="@drawable/bg_rounded"
        android:layout_marginBottom="45dp"
        android:progressTint="#7FFFD4"
        android:thumbTint="#7FFFD4"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

    <!--<ImageView
        android:id="@+id/iv_preview"
        android:layout_width="200dp"
        android:layout_height="180dp"
        android:visibility="invisible"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>-->

    <androidx.cardview.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        app:cardCornerRadius="15dp"
        app:cardElevation="20dp"
        app:cardPreventCornerOverlap="true"
        app:cardUseCompatPadding="true"
        android:visibility="invisible"
        app:layout_constraintBottom_toTopOf="@id/seekbar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <ImageView
            android:id="@+id/iv_preview"
            android:layout_width="192dp"
            android:layout_height="108dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>

    </androidx.cardview.widget.CardView>

</androidx.constraintlayout.widget.ConstraintLayout>

2. Activity文件VideoActivity.java

public class VideoActivity extends AppCompatActivity {

    private static final String TAG = "VideoActivity";

    private VideoView mVideoView;
    private SeekBar mSeekBar;
    private ImageView mImageViewPreview;
    private CardView mCardView;

    private float curProgress;
    private float MAX_PROGRESS;

    private MediaMetadataRetriever mmr;
    private Bitmap[] bitmaps;
    private static final int PREVIEW_IMG_WIDTH = 192;
    private static final int PREVIEW_IMG_HEIGHT = 108;

    private boolean isSeekBarProgress = false;

    private Handler handler = new Handler();

    private Runnable runnable = new Runnable() {

        public void run() {

            if (mVideoView.isPlaying()) {
                if (!isSeekBarProgress) {
                    int current = mVideoView.getCurrentPosition();
                    mSeekBar.setProgress(current);
                }
            }
            handler.postDelayed(runnable, 100);

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video);

        mmr = new MediaMetadataRetriever();
        mmr.setDataSource(this, Uri.parse("android.resource://"
                + getApplicationContext().getPackageName() + "/" + R.raw.vid_bigbuckbunny));

        mSeekBar = findViewById(R.id.seekbar);
        mImageViewPreview = findViewById(R.id.iv_preview);
        mVideoView = findViewById(R.id.video_view);
        mCardView = findViewById(R.id.cardView);
        mVideoView.setVideoURI(Uri.parse("android.resource://"
                + getApplicationContext().getPackageName() + "/" + R.raw.vid_bigbuckbunny));
//        MediaController mediaController = new MediaController(this);
//        mVideoView.setMediaController(mediaController);

        mVideoView.requestFocus();

        mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                mp.setLooping(true);

                MAX_PROGRESS = mVideoView.getDuration();
                mSeekBar.setMax((int) MAX_PROGRESS);

                bitmaps = new Bitmap[(int) (MAX_PROGRESS / 1000) + 1];
                Log.i(TAG, "onCreate: " + MAX_PROGRESS + "," + curProgress + "," + bitmaps.length);
                // 提前截取预览图,1s的时间间隔截取,用于快进时显示
                getPreviewImage();

                // 开始线程,更新进度条的进度
                handler.postDelayed(runnable, 0);
                mVideoView.start();
            }
        });

        mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                curProgress = 0;
                mSeekBar.setProgress(0);
            }
        });

mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        Log.i(TAG, "onProgressChanged: " + progress);
        if (isSeekBarProgress) {
            updateProgress(progress);
        }
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        isSeekBarProgress = true;
        Log.i(TAG, "onStartTrackingTouch: " + mVideoView.isPlaying());
        if (mVideoView.isPlaying()) {
            mVideoView.pause();
            updatePreviewStatus(true);
        }
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        Log.i(TAG, "onStopTrackingTouch: ");
        int pro = seekBar.getProgress();
        mVideoView.seekTo(pro);
        if (!mVideoView.isPlaying()) {
            mVideoView.seekTo(pro);
            mVideoView.start();
            updatePreviewStatus(true);
        }
        isSeekBarProgress = false;
    }
});

    }

    private void getPreviewImage() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < bitmaps.length - 1; i++) {
                    if (refreshFlag) {
                        // 获取当前快进帧图像的bitmap对象 单位是微秒
                        // 压缩图片,减少内存占用
                        bitmaps[i] = Bitmap.createScaledBitmap(
                                mmr.getFrameAtTime(i * 1000 * 1000L,
                                        MediaMetadataRetriever.OPTION_PREVIOUS_SYNC),
                                PREVIEW_IMG_WIDTH,
                                PREVIEW_IMG_HEIGHT,
                                true);
                        Log.i(TAG, "run: " + bitmaps[i].getByteCount());
                    }
                }
            }
        }).start();

    }

    @Override
    protected void onResume() {
        super.onResume(); 
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(runnable);
        mmr.release();
        mmr.close();
    }

    private void updateProgress(int pro) {
        curProgress = pro;

        if (curProgress >= MAX_PROGRESS) {
            curProgress = MAX_PROGRESS - 10;
        } else if (curProgress < 0) {
            curProgress = 0;
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mCardView.setVisibility(View.VISIBLE);
                mImageViewPreview.setImageBitmap(bitmaps[(int) (curProgress / 1000)]);
            }
        });

    }

    private void updateSeekBarStatus(final boolean b) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mSeekBar.setPressed(b);
            }
        });
    }

    private void updatePreviewStatus(final boolean b) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mCardView.setVisibility(b ? View.VISIBLE : View.INVISIBLE);
            }
        });
    }
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。