使用 LibVLC 构建自定义 Android 视频播放器:分步指南,降低延迟/图像失帧(附源码)

前言
在这篇博文中,我们将深入探讨使用 LibVLC 库的自定义 Android 视频播放器的实现细节。本分步指南将涵盖设置播放器、处理各种事件以及在您的 Android 应用程序中提供无缝视频播放体验的基本方面。

一、LibVLC 概述:

视频播放是许多 Android 应用程序中的常见功能,从流媒体服务到多媒体播放器。LibVLC 是一种流行的多媒体框架,为开发人员构建功能丰富的视频播放器提供了坚实的基础。我们将利用 LibVLC 的功能创建自定义视频播放器。

二、引入依赖

在深入研究实现之前,请确保在 Android 项目中设置了必要的依赖项。包括 LibVLC 库及其关联的依赖项。您可以在 LibVLC AAR下载地址上找到相应的AAR包并导入。

三、LibVLC 使用

1.初始化 LibVLC 实例

public class PlayerView extends FrameLayout {
    private LibVLC mLibVLC;
    private MediaPlayer mediaPlayer;

    // Constructor and other methods...

    private void init() {
        // Initialize LibVLC and MediaPlayer
        mLibVLC = LibVLCUtil.getLibVLC(CloudVlcOptions.getInstance().getOptions());
        mediaPlayer = new MediaPlayer(mLibVLC);

        // Additional setup...
    }
}

2.设置 SurfaceView

public class PlayerView extends FrameLayout {
    private SurfaceView mSurface;
    private SurfaceHolder mSurfaceHolder;

    // Constructor and other methods...

    private void init() {
        // Additional setup...

        // Set up SurfaceView
        mSurface = findViewById(R.id.player_surface);
        mSurfaceHolder = mSurface.getHolder();
        mSurfaceHolder.setFormat(PixelFormat.RGBX_8888);
        mSurfaceHolder.addCallback(surfaceViewCallback);

        // Additional surface-related configuration...
    }

    private SurfaceHolder.Callback surfaceViewCallback = new SurfaceHolder.Callback() {
        // Implement surface-related callbacks...
    };
}

3.实现事件处理

public class PlayerView extends FrameLayout {
    private MediaPlayer.EventListener eventListener;

    // Constructor and other methods...

    private void init() {
        // Additional setup...

        // Event listener
        eventListener = new MediaPlayer.EventListener() {
            @Override
            public void onEvent(MediaPlayer.Event event) {
                // Handle MediaPlayer events
            }
        };
    }
}

4.媒体播放控制

public class PlayerView extends FrameLayout {
    // Constructor and other methods...

    public void start() {
        // Start playback
        mediaPlayer.play();
    }

    public void pausePlay(boolean hasToDetach) {
        // Pause playback
        if (mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }

        // Additional handling...
    }
    public void resumePlay() {
        try {
            //设置视频界面
            vlcVout.detachViews();
            vlcVout.setVideoSurface(mSurface.getHolder().getSurface(), mSurface.getHolder());
            //设置播放窗口的尺寸
            vlcVout.setWindowSize(mSurface.getWidth(), mSurface.getHeight());
            vlcVout.addCallback(callback);
            vlcVout.setVideoView(mSurface);
            vlcVout.attachViews(this.layoutListener);
            isAttachViewsReady = true;
            mediaPlayer.setEventListener(eventListener);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void releasePlayer() {
        // Release player resources
        if (mediaPlayer != null) {
            mediaPlayer.release();
            mediaPlayer = null;
        }

        // Additional cleanup...
    }

    // Additional playback control methods...
}

5.布局和显示处理

public class PlayerView extends FrameLayout {
    // Constructor and other methods...

    private void updateVideoSurfaces() {
        // Update video surfaces based on layout and display size
    }

    private void changeMediaPlayerLayout(int displayW, int displayH) {
        // Change video placement using MediaPlayer API
    }

    // Additional layout and display handling methods...
}

结论
通过执行这些步骤,您可以使用 LibVLC 将自定义 Android 视频播放器集成到您的应用程序中。以下为LibVLC使用源码↓
1.创建LibVLC自定义View继承FrameLayout

public class PlayerView extends FrameLayout {

    private static final String TAG = "PlayerView";

    public interface OnChangeListener {
        void onBuffer(float buffer);

        void onEnd();

        void onTimeChange(MediaPlayer.Event event);

        void onNewLayout();

        void onPlaying();

        void onPause();

        void onError();

    }

    private Context mContext;
    private LibVLC mLibVLC;
    private MediaPlayer mediaPlayer;
    private IVLCVout vlcVout;
    private int videoWidth;
    private int videoHeight;
    private boolean isLocal;

    private long totalTime = 0;

    private SurfaceView mSurface;
    //private SurfaceView mSubtitlesSurface;

    private SurfaceHolder mSurfaceHolder;
    //private SurfaceHolder mSubtitlesSurfaceHolder;

    private FrameLayout mSurfaceFrame;
    private boolean isAttachViewsReady = false;

    private OnChangeListener mOnChangeListener;
    private boolean mCanSeek = false;

    private String url;

    //------------------------------
    private final Handler mHandler = new Handler();
    private View.OnLayoutChangeListener mOnLayoutChangeListener = null;
    private int mVideoHeight = 0;
    private int mVideoWidth = 0;
    private int mVideoVisibleHeight = 0;
    private int mVideoVisibleWidth = 0;
    private int mVideoSarNum = 0;
    private int mVideoSarDen = 0;

    private static final int SURFACE_BEST_FIT = 0;
    private static final int SURFACE_FIT_SCREEN = 1;
    private static final int SURFACE_FILL = 2;
    private static final int SURFACE_16_9 = 3;
    private static final int SURFACE_4_3 = 4;
    private static final int SURFACE_ORIGINAL = 5;
    private static int CURRENT_SIZE = SURFACE_BEST_FIT;
    private int screenWid, screenHei;

    public PlayerView(Context context) {
        super(context);
        mContext = context;
        init();
    }

    public PlayerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
    }

    public PlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
    }

    public void initPlayer(String url) {
        this.url = url;
        isLocal = false;
        initializeMedia();
        updateVideoSurfaces();
    }

    public void initPlayer(File file) {
        this.url = file.getAbsolutePath();
        isLocal = true;
        initializeMedia();
        updateVideoSurfaces();
    }

    private void init() {
        LayoutInflater.from(getContext()).inflate(R.layout.hz_view_player, this);
        mSurface = (SurfaceView) findViewById(R.id.player_surface);
        mSurfaceFrame = (FrameLayout) findViewById(R.id.player_surface_frame);

        mLibVLC = LibVLCUtil.getLibVLC(CloudVlcOptions.getInstance().getOptions());
        mediaPlayer = new MediaPlayer(mLibVLC);

        mSurfaceHolder = mSurface.getHolder();
        mSurfaceHolder.setFormat(PixelFormat.RGBX_8888);
        mSurfaceHolder.setKeepScreenOn(true);
        mSurfaceHolder.addCallback(surfaceViewCallback);

        //设置视频界面
        vlcVout = mediaPlayer.getVLCVout();
        vlcVout.setVideoSurface(mSurface.getHolder().getSurface(), mSurface.getHolder());
        vlcVout.setWindowSize(mSurface.getWidth(), mSurface.getHeight());
        vlcVout.addCallback(callback);
        vlcVout.setVideoView(mSurface);
        vlcVout.attachViews(this.layoutListener);
        //设置播放窗口的尺寸

        PixelFormat info = new PixelFormat();
        PixelFormat.getPixelFormatInfo(PixelFormat.RGBX_8888, info);

        isAttachViewsReady = true;

        mediaPlayer.setEventListener(eventListener);

        if (mOnLayoutChangeListener == null) {
            mOnLayoutChangeListener = new View.OnLayoutChangeListener() {
                private final Runnable mRunnable = new Runnable() {
                    @Override
                    public void run() {
                        updateVideoSurfaces();
                    }
                };

                @Override
                public void onLayoutChange(View v, int left, int top, int right,
                                           int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
                        mHandler.removeCallbacks(mRunnable);
                        mHandler.post(mRunnable);
                    }
                }
            };
        }
        mSurfaceFrame.addOnLayoutChangeListener(mOnLayoutChangeListener);
    }

    SurfaceHolder.Callback surfaceViewCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {

        }

        @Override
        public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {

        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
            releasePlayer(); // 在这里调用销毁播放器的方法
        }
    };

    private IVLCVout.Callback callback = new IVLCVout.Callback() {
        @Override
        public void onSurfacesCreated(IVLCVout ivlcVout) {

        }

        @Override
        public void onSurfacesDestroyed(IVLCVout ivlcVout) {

        }

    };

    private IVLCVout.OnNewVideoLayoutListener layoutListener = new IVLCVout.OnNewVideoLayoutListener() {
        @Override
        public void onNewVideoLayout(IVLCVout ivlcVout, int i, int i1, int i2, int i3, int i4, int i5) {
            try {
                screenWid = i;
                screenHei = i1;
                totalTime = mediaPlayer.getLength();
                mCanSeek = mediaPlayer.isSeekable();
                videoWidth = i;
                videoHeight = i1;
                LogUtil.e("--------onNewLayout------------");

                if (mOnChangeListener != null) {
                    mOnChangeListener.onNewLayout();
                }
                //-------------------------
                mVideoWidth = i;
                mVideoHeight = i1;
                mVideoVisibleWidth = i2;
                mVideoVisibleHeight = i3;
                mVideoSarNum = i4;
                mVideoSarDen = i5;
                updateVideoSurfaces();
                Log.d(TAG, "Video dimensions: " + videoWidth + "x" + videoHeight);

            } catch (Exception e) {
                Log.d("vlc-newlayout", e.toString());
            }
        }
    };

    private MediaPlayer.EventListener eventListener = new MediaPlayer.EventListener() {
        @Override
        public void onEvent(MediaPlayer.Event event) {
            try {
                if (event.type == MediaPlayer.Event.Buffering) {
                    if (mOnChangeListener != null) {
                        mOnChangeListener.onBuffer(event.getBuffering());
                    }
                } else if (event.type == MediaPlayer.Event.TimeChanged) {
                    if (mOnChangeListener != null) {
                        mOnChangeListener.onTimeChange(event);
                    }
                } else if (event.type == MediaPlayer.Event.Playing) {
                    if (mOnChangeListener != null) {
                        mOnChangeListener.onPlaying();
                    }
                } else if (event.type == MediaPlayer.Event.Paused) {
                    if (mOnChangeListener != null) {
                        mOnChangeListener.onPause();
                    }
                } else if (event.type == MediaPlayer.Event.EncounteredError) {
                    if (mOnChangeListener != null) {
                        mOnChangeListener.onError();
                    }
                }
                //播放结束
                if (mediaPlayer.getPlayerState() == Media.State.Ended && event.type == MediaPlayer.Event.EndReached) {
                    if (mOnChangeListener != null) {
                        mOnChangeListener.onEnd();
                    }
                }
            } catch (Exception e) {
                Log.d("vlc-event", e.toString());
            }
        }
    };

    public void setOnChangeListener(OnChangeListener listener) {
        mOnChangeListener = listener;
    }

    public void changeSurfaceSize(int width) {
        this.screenWid = width;
        this.screenHei = videoHeight;
        ViewGroup.LayoutParams layoutParams = mSurface.getLayoutParams();
        layoutParams.width = width;
        layoutParams.height = (int) Math.ceil((float) videoHeight * (float) width / (float) videoWidth);
        mSurface.setLayoutParams(layoutParams);
    }

    public void changeSurfaceSize(int width, int height) {
        this.screenWid = width;
        this.screenHei = height;
        Log.d(TAG, "Video dimensions: " + videoWidth + "x" + videoHeight);

        ViewGroup.LayoutParams layoutParams = mSurface.getLayoutParams();
        float max = Math.max((float) videoWidth / (float) width, (float) videoHeight / (float) height);
        layoutParams.width = (int) Math.ceil((float) videoWidth / max);
        layoutParams.height = (int) Math.ceil((float) videoHeight / max);
        mSurfaceHolder.setFixedSize(videoWidth, videoHeight);
        mSurface.setLayoutParams(layoutParams);
    }

    public void setZOrderMediaOverlay(boolean isOverlay) {
        mSurface.setZOrderMediaOverlay(isOverlay);
    }

    public void start() {
        try {
            if (mediaPlayer.getPlayerState() == Media.State.Ended) {
                restart();
            } else if (!mediaPlayer.isPlaying()) {
                mediaPlayer.play();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void restart() {
        if (mediaPlayer.getPlayerState() == Media.State.Ended) {
            Media media = null;
            if (isLocal) {
                media = new Media(mLibVLC, url);
            } else {
                media = new Media(mLibVLC, Uri.parse(url));
            }
            setupMediaOptions(media);
            mediaPlayer.setMedia(media);
        }
        mediaPlayer.play();
    }
    private void initializeMedia() {
        Media media = null;
        if (isLocal) {
            media = new Media(mLibVLC, url);
        } else {
            media = new Media(mLibVLC, Uri.parse(url));
        }
        if (mediaPlayer.getMedia() == null) {
            setupMediaOptions(media);
            mediaPlayer.setMedia(media);
        }
    }

    private Media createMedia(String mediaUrl) {
        return new Media(mLibVLC, Uri.parse(mediaUrl));
    }
    private void setupMediaOptions(Media media) {
        int cache = 300;
        media.addOption(":network-caching=" + cache);
        media.addOption(":file-caching=" + cache);
        media.addOption(":live-caching=" + cache);
        media.addOption(":sout-mux-caching=" + cache);
        media.addOption(":codec=mediacodec,iomx,all");
        media.setHWDecoderEnabled(true, false);
    }
    public void pausePlay(boolean hasToDetach) {
        try {
            if(mediaPlayer!=null){
                if (mediaPlayer.isPlaying()) {
                    mediaPlayer.pause();
                    //imgPlay.setBackgroundResource(R.drawable.videoviewx_play);
                }
                if (hasToDetach) {
                    vlcVout.detachViews();
                    vlcVout.removeCallback(callback);
                    mediaPlayer.setEventListener(null);
                    isAttachViewsReady = false;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void resumePlay() {
        try {
            //设置视频界面
            vlcVout.detachViews();
            vlcVout.setVideoSurface(mSurface.getHolder().getSurface(), mSurface.getHolder());
            //设置播放窗口的尺寸
            vlcVout.setWindowSize(mSurface.getWidth(), mSurface.getHeight());
            vlcVout.addCallback(callback);
            vlcVout.setVideoView(mSurface);
            vlcVout.attachViews(this.layoutListener);
            isAttachViewsReady = true;
            mediaPlayer.setEventListener(eventListener);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void releasePlayer() {
        try {
            if (mediaPlayer != null) {
                mediaPlayer.release();
                mediaPlayer = null;
            }
            if (mLibVLC != null) {
                mLibVLC.release();
                mLibVLC = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public boolean isAttachViewsReady() {
        return isAttachViewsReady;
    }

    public long getTime() {
        return mediaPlayer.getTime();
    }

    public long getLength() {
        return mediaPlayer.getLength();
    }

    public void setTime(long time) {
        mediaPlayer.setTime(time);
    }

    public void setNetWorkCache(int time) {
        //mediaPlayer.setNetworkCaching(time);
    }

    public boolean isPlaying() {
        if(mediaPlayer!=null){
            return mediaPlayer.isPlaying();
        }
        return false;
    }

    public boolean isSeekable() {
        return mediaPlayer.isSeekable();
    }

    public int getVolume() {
        return mediaPlayer.getVolume();
    }

    public void setVolume(int volume) {
        mediaPlayer.setVolume(volume);
    }

    public void seek(int delta) {
        if (mediaPlayer.getLength() <= 0 || !mCanSeek)
            return;

        if (mediaPlayer.getPlayerState() == Media.State.Ended) {
            restart();
        }
        long position = delta;
        if (position < 0)
            position = 0;
        mediaPlayer.setTime(position);
    }

    private void updateVideoSurfaces() {
//        int sw = getWindow().getDecorView().getWidth();
//        int sh = getWindow().getDecorView().getHeight();
        int sw = mSurface.getWidth();
        int sh = mSurface.getHeight();
        // sanity check
        if (sw * sh == 0) {
            Log.e(TAG, "Invalid surface size");
            return;
        } else {
            Log.e(TAG, "surface size" + sw + "*--*" + sh);
        }

        if (mediaPlayer.getVLCVout() != null) {
            mediaPlayer.getVLCVout().setWindowSize(sw, sh);
        }

        ViewGroup.LayoutParams lp = mSurface.getLayoutParams();
        if (mVideoWidth * mVideoHeight == 0) {
            /* Case of OpenGL vouts: handles the placement of the video using MediaPlayer API */
            lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
            lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
            mSurface.setLayoutParams(lp);
            lp = mSurfaceFrame.getLayoutParams();
            lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
            lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
            mSurfaceFrame.setLayoutParams(lp);
            changeMediaPlayerLayout(sw, sh);
            return;
        }

        if (lp.width == lp.height && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
            /* We handle the placement of the video using Android View LayoutParams */
            mediaPlayer.setAspectRatio(null);
            mediaPlayer.setScale(0);
        }

        double dw = sw, dh = sh;
        final boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;

        if (sw > sh && isPortrait || sw < sh && !isPortrait) {
            dw = sh;
            dh = sw;
        }

        // compute the aspect ratio
        double ar, vw;
        if (mVideoSarDen == mVideoSarNum) {
            /* No indication about the density, assuming 1:1 */
            vw = mVideoVisibleWidth;
            ar = (double) mVideoVisibleWidth / (double) mVideoVisibleHeight;
        } else {
            /* Use the specified aspect ratio */
            vw = mVideoVisibleWidth * (double) mVideoSarNum / mVideoSarDen;
            ar = vw / mVideoVisibleHeight;
        }

        // compute the display aspect ratio
        double dar = dw / dh;

        switch (CURRENT_SIZE) {
            case SURFACE_BEST_FIT:
                if (dar < ar)
                    dh = dw / ar;
                else
                    dw = dh * ar;
                break;
            case SURFACE_FIT_SCREEN:
                if (dar >= ar)
                    dh = dw / ar; /* horizontal */
                else
                    dw = dh * ar; /* vertical */
                break;
            case SURFACE_FILL:
                break;
            case SURFACE_16_9:
                ar = 16.0 / 9.0;
                if (dar < ar)
                    dh = dw / ar;
                else
                    dw = dh * ar;
                break;
            case SURFACE_4_3:
                ar = 4.0 / 3.0;
                if (dar < ar)
                    dh = dw / ar;
                else
                    dw = dh * ar;
                break;
            case SURFACE_ORIGINAL:
                dh = mVideoVisibleHeight;
                dw = vw;
                break;
        }

        // set display size
        lp.width = (int) Math.ceil(dw * mVideoWidth / mVideoVisibleWidth);
        lp.height = (int) Math.ceil(dh * mVideoHeight / mVideoVisibleHeight);
        mSurface.setLayoutParams(lp);

        // set frame size (crop if necessary)
        lp = mSurfaceFrame.getLayoutParams();
        lp.width = (int) Math.floor(dw);
        lp.height = (int) Math.floor(dh);
        mSurfaceFrame.setLayoutParams(lp);

        mSurface.invalidate();
    }


    private void changeMediaPlayerLayout(int displayW, int displayH) {
        /* Change the video placement using the MediaPlayer API */
        switch (CURRENT_SIZE) {
            case SURFACE_BEST_FIT:
                try {
                    mediaPlayer.setAspectRatio(null);
                    mediaPlayer.setScale(0);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
            case SURFACE_FIT_SCREEN:
            case SURFACE_FILL: {
                Media.VideoTrack vtrack = mediaPlayer.getCurrentVideoTrack();
                if (vtrack == null)
                    return;
                final boolean videoSwapped = vtrack.orientation == Media.VideoTrack.Orientation.LeftBottom
                        || vtrack.orientation == Media.VideoTrack.Orientation.RightTop;
                if (CURRENT_SIZE == SURFACE_FIT_SCREEN) {
                    int videoW = vtrack.width;
                    int videoH = vtrack.height;

                    if (videoSwapped) {
                        int swap = videoW;
                        videoW = videoH;
                        videoH = swap;
                    }
                    if (vtrack.sarNum != vtrack.sarDen)
                        videoW = videoW * vtrack.sarNum / vtrack.sarDen;

                    float ar = videoW / (float) videoH;
                    float dar = displayW / (float) displayH;

                    float scale;
                    if (dar >= ar)
                        scale = displayW / (float) videoW; /* horizontal */
                    else
                        scale = displayH / (float) videoH; /* vertical */
                    mediaPlayer.setScale(scale);
                    mediaPlayer.setAspectRatio(null);
                } else {
                    mediaPlayer.setScale(0);
                    mediaPlayer.setAspectRatio(!videoSwapped ? "" + displayW + ":" + displayH
                            : "" + displayH + ":" + displayW);
                }
                break;
            }
            case SURFACE_16_9:
                mediaPlayer.setAspectRatio("16:9");
                mediaPlayer.setScale(0);
                break;
            case SURFACE_4_3:
                mediaPlayer.setAspectRatio("4:3");
                mediaPlayer.setScale(0);
                break;
            case SURFACE_ORIGINAL:
                mediaPlayer.setAspectRatio(null);
                mediaPlayer.setScale(1);
                break;
        }
    }
}

2.自定义View的XML

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/player_surface_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black">

    <!--
     the double FrameLayout is necessary here to do cropping on the bottom right
     (which requires the surface not be centered), while keeping the result centered
    -->
    <SurfaceView
        android:id="@+id/player_surface"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center" />
</FrameLayout>

3.PlayerView中引用的‘LibVLCUtil’ VLC设置项已单例模式创建

public class LibVLCUtil {
    private static LibVLC libVLC = null;

    public synchronized static LibVLC getLibVLC(ArrayList<String> options) throws IllegalStateException {
        if (libVLC == null) {
            if (options == null) {
                libVLC = new LibVLC(MyApplication.getInstance(),new ArrayList<String>());
            } else {
                libVLC = new LibVLC(MyApplication.getInstance(),options);
            }
        }
        return libVLC;
    }
}

4.PlayerView中引用的‘CloudVlcOptions’ VLC设置项已单例模式创建

public class CloudVlcOptions {
    private static CloudVlcOptions instance=null;
    private ArrayList<String> list=null;
    private CloudVlcOptions(){
        this.list=new ArrayList<>();
        list.add(false ? "--audio-time-stretch" : "--no-audio-time-stretch");
        list.add("--audio-resampler");
        list.add("--aout=opensles");
        list.add("--audio-time-stretch");
        list.add("--network-caching="+300);
        list.add("--clock-jitter="+300);
        list.add("--file-caching=" + 300);
        list.add("--demux=avformat");

        list.add("-vvv");
    }

    public static CloudVlcOptions getInstance(){
        if(instance==null){
            instance=new CloudVlcOptions();
        }
        return instance;
    }
    public ArrayList<String> getOptions(){
        return list;
    }
}

如有出现图像失帧或出现libvlc input source: attachment of directory-extractor failed...类似的错误,可以尝试添加"--demux=avformat",或查看VLC官网议题:图像失帧,尝试是否对你有利。

5.Activity中使用

public class VideoPlayerTestActivity extends BaseActivity implements View.OnClickListener {

    private String mUrl;
    private File file;
    private boolean isChangeDisplay = false;
    private long timeCount = 0;

    private PlayerView mPlayerView;
    private RelativeLayout durationBarRl;
    private ImageView playIconIv;
    private TextView curDurationTv;
    private TextView totalDurationTv;
    private SeekBar progressSeekBar;
    private ImageView shareIv;

    private SimpleDateFormat sp = new SimpleDateFormat("mm:ss");

    private boolean hasSetVideoPosition = false;
    //发送心跳
    private Timer mHeartBeatTimer = null;
    private boolean isVisFront = false;

    @Override
    protected int getContentViewId() {
        return R.layout.video_player;
    }

    @Override
    protected void initView() {
        super.initView();
        DeviceTools.hideNavigationBar(this);
        mUrl = getIntent().getStringExtra("url");
        file = (File) getIntent().getSerializableExtra("file");

        if (TextUtils.isEmpty(mUrl)) {
            shareIv.setVisibility(View.VISIBLE);
        } else {
            shareIv.setVisibility(View.GONE);
        }
        initPlayer();
        if(Config.CURRENT_DEVICE_TYPE != null){
            if(Config.CURRENT_DEVICE_TYPE.equals(Type.DvrType.SG20_B.getVal()) || Config.CURRENT_DEVICE_TYPE.equals(Type.DvrType.SG18A.getVal())){
                progressSeekBar.setEnabled(false);
            }else {
                progressSeekBar.setEnabled(true);
                initSeekBar();
            }
        }else {
            progressSeekBar.setEnabled(true);
            initSeekBar();
        }

    }

    @Override
    protected void findViews() {
        super.findViews();
        mPlayerView = findViewById(R.id.pv_video);
        durationBarRl = findViewById(R.id.rl_duration_bar);
        playIconIv = findViewById(R.id.iv_play_icon);
        curDurationTv = findViewById(R.id.tv_cur_duration);
        totalDurationTv = findViewById(R.id.tv_total_duration);
        progressSeekBar = findViewById(R.id.seek_bar_progress);
        shareIv = findViewById(R.id.iv_share);

        findViewById(R.id.iv_close).setOnClickListener(this);
        mPlayerView.setOnClickListener(this);
        playIconIv.setOnClickListener(this);
        shareIv.setOnClickListener(this);
    }
    private void initSeekBar() {
        progressSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            boolean isUser = false;

            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                isUser = b;
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                if (isUser) {
                    mPlayerView.seek(seekBar.getProgress());
                }
            }
        });
    }

    private void share() {
        if (file == null) {
            return;
        }
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
        intent.setType("*/*");
        startActivity(intent);
    }

    private void initPlayer() {
        //初始化播放器
        if (!TextUtils.isEmpty(mUrl)) {
            mPlayerView.initPlayer(mUrl);
        }
        if (file != null) {
            mPlayerView.initPlayer(file);
        }
        //设置事件监听,监听缓冲进度等
        mPlayerView.setOnChangeListener(new PlayerView.OnChangeListener() {

            @Override
            public void onBuffer(float buffer) {
                if (buffer != 100) {
                    LoadingUtil.showLoadingDialog();
                } else {
                    LoadingUtil.hideLoadingDialog();
                    if (hasSetVideoPosition) {
                        hasSetVideoPosition = false;
                    }
                }
            }

            @Override
            public void onEnd() {
                playIconIv.setImageResource(R.mipmap.hz_dvr_icon_play);
                ToastUtils.showToast(VideoPlayerTestActivity.this, MyApplication.getAppResources().getString(R.string.play_end));
                LoadingUtil.hideLoadingDialog();
                progressSeekBar.setProgress(progressSeekBar.getMax());
                if (mPlayerView.getLength() > 0) {
                    Date curDate = new Date(mPlayerView.getLength());
                    curDurationTv.setText(sp.format(curDate));
                }
            }

            @Override
            public void onTimeChange(MediaPlayer.Event event) {
                progressSeekBar.setProgress((int) mPlayerView.getTime());
                Date curDate = new Date(mPlayerView.getTime());
                curDurationTv.setText(sp.format(curDate));
            }

            @Override
            public void onNewLayout() {
                onPlayerLoadComplete();
                mPlayerView.changeSurfaceSize(DeviceTools.getWindowHeight(),DeviceTools.getWindowWidth());
                LogUtil.e("----------onNewLayout--------------");
                if (isChangeDisplay) {

                    isChangeDisplay = false;
                    mPlayerView.start();
                }
            }

            @Override
            public void onPlaying() {
                playIconIv.setImageResource(R.mipmap.hz_dvr_icon_pause);
            }

            @Override
            public void onPause() {
                playIconIv.setImageResource(R.mipmap.hz_dvr_icon_play);
            }

            @Override
            public void onError() {

            }
        });
        // 开始播放
        mPlayerView.start();
        hasSetVideoPosition = true;
    }

    private void onPlayerLoadComplete() {
        Date curDate = new Date(mPlayerView.getTime());
        Date totalDate = new Date(mPlayerView.getLength());
        curDurationTv.setText(sp.format(curDate));
        if (mPlayerView.getLength() > 0) {
            totalDurationTv.setText(sp.format(totalDate));
            progressSeekBar.setMax((int) mPlayerView.getLength());
            Drawable drawable = new BitmapDrawable(getResources(), BitmapFactory.decodeResource(getResources(), R.mipmap.hz_box_dvr_video_slider));
            progressSeekBar.setThumb(drawable);
        } else {
            progressSeekBar.setMax((int) timeCount);
            totalDurationTv.setText(sp.format(timeCount));
        }
        progressSeekBar.setProgress((int) mPlayerView.getTime());

    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mPlayerView!=null){
            mPlayerView.releasePlayer();
        }
    }

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

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

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
                case R.id.pv_video:
                    isVisFront = !isVisFront;
                    this.durationBarRl.setVisibility(isVisFront?View.GONE:View.VISIBLE);
                    break;
                case R.id.iv_close:
                    finish();
                    break;
                case R.id.iv_play_icon:
                    if (mPlayerView.isPlaying()) {
                        mPlayerView.pausePlay(false);
                        playIconIv.setImageResource(R.mipmap.hz_dvr_icon_play);
                    } else {
                        mPlayerView.start();
                        playIconIv.setImageResource(R.mipmap.hz_dvr_icon_pause);
                    }
                    break;
                case R.id.iv_share:
                    share();
                    break;
            }
    }

}

6.Activity的XML

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tool="http://schemas.android.com/tools"
    android:id="@+id/rl_parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.corelink.page.widget.PlayerView
        android:id="@+id/pv_video"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <RelativeLayout
        android:id="@+id/rl_top_bar"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:background="@color/black_alpha_50_percent"
        android:paddingLeft="15dp"
        android:paddingRight="15dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:text="@string/camera_view"
            android:textColor="@color/white_ffffff"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/tv_date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textColor="@color/white_ffffff"
            android:textSize="14sp" />

        <ImageView
            android:id="@+id/iv_close"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true"
            android:paddingLeft="13dp"
            android:src="@mipmap/hz_dvr_close_setting_window" />

        <ImageView
            android:id="@+id/iv_share"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_toLeftOf="@id/iv_close"
            android:paddingRight="13dp"
            android:src="@mipmap/hz_dvr_file_icon_share" />

    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/rl_duration_bar"
        android:layout_width="290dp"
        android:layout_height="34dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="8dp"
        android:background="@color/black_alpha_50_percent"
        android:paddingLeft="7dp">

        <ImageView
            android:id="@+id/iv_play_icon"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:paddingRight="16dp"
            android:src="@mipmap/hz_dvr_icon_pause" />

        <TextView
            android:id="@+id/tv_cur_duration"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/iv_play_icon"
            android:textColor="@color/white_ffffff"
            android:textSize="12sp"
            tool:text="00:00" />

        <TextView
            android:id="@+id/tv_total_duration"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:paddingRight="7dp"
            android:textColor="@color/white_ffffff"
            android:textSize="12sp"
            tool:text="00:00" />

        <SeekBar
            android:id="@+id/seek_bar_progress"
            style="@style/seek_bar_vol1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:layout_marginLeft="11dp"
            android:layout_marginRight="11dp"
            android:layout_toLeftOf="@id/tv_total_duration"
            android:layout_toRightOf="@id/tv_cur_duration" />

    </RelativeLayout>

</RelativeLayout>

7.资源文件

hz_dvr_icon_play
hz_dvr_icon_pause
hz_box_dvr_video_slider
hz_dvr_close_setting_window
hz_dvr_file_icon_share

四、总结

使用 LibVLC 构建功能强大的 Android 视频播放器为开发人员提供了处理多媒体内容的多功能解决方案。按照这篇博文中概述的步骤,您可以创建具有高级播放控件、事件处理和布局调整功能丰富的视频播放器。LibVLC 的强大功能使其成为在 Android 应用程序中为用户提供无缝多媒体体验的绝佳选择。

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Android使用 libvlc 播放视频文件非常简单。libvlc 是一个强大的开源多媒体框架,它支持几乎所有常见的视频和音频格式。 首先,需要将 libvlc 添加到 Android 项目中。可以通过在项目的 build.gradle 文件中添加以下代码来实现: ```groovy dependencies { implementation 'org.videolan.android:libvlc-all:3.3.0' } ``` 在需要使用 libvlc 的 Activity 中,需要先初始化 libvlc。可以在 onCreate 方法中添加以下代码: ```java LibVLC libVLC = new LibVLC(); ``` 接下来,需要创建一个SurfaceView来显示视频。可以在布局文件中添加一个SurfaceView视图: ```xml <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 然后,在 Activity 中获取 SurfaceView 并设置播放器使用它来显示视频: ```java SurfaceView surfaceView = findViewById(R.id.surfaceView); IVLCVout ivlcVout = libVLC.getVLCVout(); ivlcVout.setVideoView(surfaceView); ivlcVout.attachViews(); ``` 最后,需要创建一个 MediaPlayer 实例来控制视频的播放。可以在 Activity 的 onCreate 方法中添加以下代码: ```java String filePath = "/path/to/video/file"; MediaPlayer mediaPlayer = new MediaPlayer(libVLC); mediaPlayer.setMedia(Uri.parse(filePath)); ``` 现在,可以使用 MediaPlayer 对象来控制视频的播放。例如,可以调用 mediaPlayer.play() 方法来开始播放视频。 当不需要播放视频时,可以释放 MediaPlayer 和 libvlc 资源,以释放内存和资源: ```java mediaPlayer.release(); libVLC.release(); ``` 以上就是如何在 Android使用 libvlc 播放视频文件的简单步骤。通过使用 libvlc,我们可以轻松实现高度定制的视频播放器,并支持各种视频格式和功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android程序Su

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值