Android m3u8网络视频播放

您所在的位置:网站首页 安卓切片 Android m3u8网络视频播放

Android m3u8网络视频播放

2023-10-01 08:13| 来源: 网络整理| 查看: 265

最近在做 m3u8网络视频播放,踩了不少坑,也试了不少的 框架,特别记录一下其中用的比较多的三种

第一种:media:ijkplayer

media:ijkplayer 是由 bilbil 提供的开源的视频 框架,但是由过之后感觉不太好用:

优点: 1、支持 Android 和 IOS 2、支持多种视频的硬解码

缺点: 1、加载时间过长;从开始加载 到 开始播放 第一帧视频,中间最少需要十秒时间(一开始以为是自己的配置有问题,但在网上找了一下,但都没有找到好的解决方案) 2、不支持实时视频截图(由于项目的需要,需要关闭时,最后显示的视频截图,但看了一下源码,并没有找到提供相关源码)

如果有哪位大神解决的,麻烦给留个言!!!!!

media:ijkplayer 的github地址:https://github.com/bilibili/ijkplayer

集成方式:

// required, enough for most devices. implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8' // Other ABIs: optional implementation 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8' // ExoPlayer as IMediaPlayer: optional, experimental implementation 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8'

混淆配置:

-keep class tv.danmaku.ijk.media.player.** { *; }

使用: 1):IjkVideoView

public class IjkVideoView extends FrameLayout { private Context mContext;//上下文 private IMediaPlayer mMediaPlayer = null;//视频控制类 private VideoPlayerListener mVideoPlayerListener;//自定义监听器 private SurfaceView mSurfaceView;//播放视图 private String mPath = "";//视频文件地址 public IjkVideoView(@NonNull Context context) { super(context); initVideoView(context); } public IjkVideoView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); initVideoView(context); } public IjkVideoView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initVideoView(context); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public IjkVideoView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public abstract static class VideoPlayerListener implements IMediaPlayer.OnPreparedListener, IMediaPlayer.OnCompletionListener, IMediaPlayer.OnErrorListener { } private void initVideoView(Context context) { mContext = context; setFocusable(true); } public void setPath(String path) { if (TextUtils.equals("", mPath)) { mPath = path; initSurfaceView(); } else { mPath = path; loadVideo(); } } private void initSurfaceView() { mSurfaceView = new SurfaceView(mContext); mSurfaceView.getHolder().addCallback(new LmnSurfaceCallback()); LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER); mSurfaceView.setLayoutParams(layoutParams); this.addView(mSurfaceView); } //surfaceView的监听器 private class LmnSurfaceCallback implements SurfaceHolder.Callback { @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { loadVideo(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { } } //加载视频 private void loadVideo() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.release(); } IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer(); mMediaPlayer = ijkMediaPlayer; if (mVideoPlayerListener != null) { mMediaPlayer.setOnPreparedListener(mVideoPlayerListener); mMediaPlayer.setOnErrorListener(mVideoPlayerListener); } try { mMediaPlayer.setDataSource(mPath); } catch (IOException e) { e.printStackTrace(); } mMediaPlayer.setDisplay(mSurfaceView.getHolder()); mMediaPlayer.prepareAsync(); } public void setListener(VideoPlayerListener listener) { this.mVideoPlayerListener = listener; if (mMediaPlayer != null) { mMediaPlayer.setOnPreparedListener(listener); } } public boolean isPlaying() { if (mMediaPlayer != null) { return mMediaPlayer.isPlaying(); } return false; } public void start() { if (mMediaPlayer != null) { mMediaPlayer.start(); } } public void pause() { if (mMediaPlayer != null) { mMediaPlayer.pause(); } } public void stop() { if (mMediaPlayer != null) { mMediaPlayer.stop(); } } public void reset() { if (mMediaPlayer != null) { mMediaPlayer.reset(); } } public void release() { if (mMediaPlayer != null) { mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; } } }

2):布局

3):Activity

public class IjkPlayerVideoActivity extends Activity { private AlertDialog alertDialog; private String path; private IjkVideoView ijkVideoView; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); path = getIntent().getStringExtra("ADDRESS"); setContentView(R.layout.ijkplayervideo_layout); findViewById(R.id.back).setOnClickListener(v -> onBackPressed()); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("播放错误"); builder.setNegativeButton("重试", (dialogInterface, i) -> setLiveParam()); builder.setPositiveButton("退出", (dialogInterface, i) -> finish()); alertDialog = builder.create(); alertDialog.setCancelable(false); alertDialog.setCanceledOnTouchOutside(false); setLiveParam(); } private void setLiveParam() { findViewById(R.id.progressBar).setVisibility(View.VISIBLE); IjkMediaPlayer.loadLibrariesOnce(null); IjkMediaPlayer.native_profileBegin("libijkplayer.so"); //监听 ijkVideoView=findViewById(R.id.jkVideoView); ijkVideoView.setListener(new IjkVideoView.VideoPlayerListener() { @Override public void onPrepared(IMediaPlayer mp) { //播放成功处理 mp.start(); findViewById(R.id.progressBar).setVisibility(View.INVISIBLE); } @Override public void onCompletion(IMediaPlayer iMediaPlayer) { } @Override public boolean onError(IMediaPlayer mp, int what, int extra) { ToastUtils.showToast("播放失败"); finish(); return true; } }); // path = "你自己的播放地址"; //路径 ijkVideoView.setPath(path); ijkVideoView.start(); } @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) ijkVideoView.getLayoutParams(); //横屏 if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { params.height = RelativeLayout.LayoutParams.MATCH_PARENT; } else { //竖屏 params.height = (int) UnitUtils.dp2px(this, 300); } ijkVideoView.setLayoutParams(params); } @Override protected void onDestroy() { IjkMediaPlayer.native_profileEnd(); if (alertDialog != null && alertDialog.isShowing()) { alertDialog.dismiss(); } ijkVideoView.stop(); super.onDestroy(); } @Override protected void onResume() { super.onResume(); findViewById(R.id.progressBar).setVisibility(View.VISIBLE); if (!ijkVideoView.isPlaying()) { ijkVideoView.start(); } } @Override protected void onPause() { super.onPause(); ijkVideoView.pause(); } } 第二种:Vitamio

Vitamio是适用于Android和iOS的开放式多媒体框架,具有完整,真实的硬件加速解码器和渲染器。

新的功能:

支持大多数FFmpeg AVOptions,从而启用自定义HTTP标头支持。支持更多的硬件,例如X86或MIPS。改善流媒体,特别是支持自适应比特率流媒体,需要手动打开。包含OpenSSL,因此支持某些与SSL相关的协议,例如https,tls,rtmps,rtmpts。播放速度控制从0.5倍到2.0倍。改进的字幕支持,包括外部位图字幕。在线视频缓存到本地存储中,并且可以重复使用,直到删除缓存文件为止。更多MediaPlayer API,例如getMetadata,getVideoTrack。完整的Java代码对所有开发人员开放,欢迎修改和贡献。 10.支持RGBA_8888渲染​​,支持将RGB_565或RGBA_8888切换到视频渲染。

Vitamio 感觉是强大的,播放速度也很快,也可以截图,但在使用过程也遇到了问题: 根据文档的提示,Vitamio 是可以 支持 https 的,但使用的结果是 https 无法播放,所以放弃了使用,也不知道是不是因为安全证书出了问题,还有一个问题就是,Vitamio 最早是更新于七年前,很多方法已经过时了,不知道还有没有人在维护。

Vitamio gitHub地址:https://github.com/yixia/VitamioBundleStudio

使用: 1)、解压文件,将其中的vitamio导入到as中 在这里插入图片描述 其中的vitamio-sample是官方提供的demo,而我们要导入as的是vitamio.

2)、打开AS,File -> New -> Import Moudle,选择刚才解压文件夹下的 vitamio 文件. 导入后的文件目录中会多出vitamin文件夹,如下图 在这里插入图片描述 3)、在 dependencies 中添加 compile project(’:vitamio’) 如果你导入module中更改过名字的话 要改成修改后的名字 如图: 在这里插入图片描述 混淆配置:

# For Vitamio classes -keep class io.vov.utils.** { *; } -keep class io.vov.vitamio.** { *; }

4)、打开app/src/main目录下的AndroidManifest.xml,注册io.vov.vitamio.activity.InitActivity

注意:这个InitActivity存在于vitamio/src/对应的目录下,不需要用户编写.

添加权限:

至此,vitamio导入完毕.

5)、VideoView控件的使用

public class VideoActivity extends Activity implements MediaPlayer.OnInfoListener { private VideoView mVideoView; private AlertDialog alertDialog; private int errorCount; private int requestCode; private String shotsName; private String path; private MediaPlayer mediaPlayer; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); Vitamio.isInitialized(this); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.vitamio_layout); mVideoView = findViewById(R.id.buffer); findViewById(R.id.back).setOnClickListener(v -> onBackPressed()); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("播放错误"); builder.setNegativeButton("重试", (dialogInterface, i) -> { setLiveParam(); mVideoView.resume(); }); builder.setPositiveButton("退出", (dialogInterface, i) -> finish()); alertDialog = builder.create(); alertDialog.setCancelable(false); alertDialog.setCanceledOnTouchOutside(false); setLiveParam(); } private void setLiveParam() { errorCount = 0; findViewById(R.id.progressBar).setVisibility(View.VISIBLE); Intent intent = getIntent(); path = intent.getStringExtra("ADDRESS"); requestCode = intent.getIntExtra("requestCode", 200); shotsName = intent.getStringExtra("shotsName"); Uri uri = Uri.parse(path); mVideoView.setVideoURI(uri); mVideoView.setBufferSize(1024); mVideoView.setHardwareDecoder(true); mVideoView.requestFocus(); mVideoView.setOnInfoListener(this); mVideoView.setOnPreparedListener(mediaPlayer -> { // optional need Vitamio 4.0 mediaPlayer.setPlaybackSpeed(1.0f); }); mVideoView.setOnErrorListener((mediaPlayer, i, i1) -> { errorCount++; if (errorCount > 3) { String errMsg = "MEDIA_ERROR_UNKNOWN"; switch (i) { case MediaPlayer.MEDIA_ERROR_UNKNOWN: errMsg = "MEDIA_ERROR_UNKNOWN"; break; case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: errMsg = "MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK"; break; case MediaPlayer.MEDIA_ERROR_IO: errMsg = "MEDIA_ERROR_IO"; break; case MediaPlayer.MEDIA_ERROR_MALFORMED: errMsg = "MEDIA_ERROR_MALFORMED"; break; case MediaPlayer.MEDIA_ERROR_UNSUPPORTED: errMsg = "MEDIA_ERROR_UNSUPPORTED"; break; case MediaPlayer.MEDIA_ERROR_TIMED_OUT: errMsg = "MEDIA_ERROR_TIMED_OUT"; break; } alertDialog.setTitle("播放错误:" + errMsg); alertDialog.show(); } else { new Handler().postDelayed(this::setLiveParam, 1000); } return true; }); } @Override protected void onDestroy() { if (alertDialog != null && alertDialog.isShowing()) alertDialog.dismiss(); super.onDestroy(); } @Override protected void onResume() { super.onResume(); findViewById(R.id.progressBar).setVisibility(View.VISIBLE); } @Override public boolean onInfo(MediaPlayer mp, int what, int extra) { mediaPlayer = mp; switch (what) { case MediaPlayer.MEDIA_INFO_BUFFERING_START: if (mVideoView.isPlaying()) { mVideoView.pause(); } findViewById(R.id.progressBar).setVisibility(View.VISIBLE); break; case MediaPlayer.MEDIA_INFO_BUFFERING_END: mVideoView.start(); findViewById(R.id.progressBar).setVisibility(View.INVISIBLE); break; case MediaPlayer.MEDIA_INFO_DOWNLOAD_RATE_CHANGED: LogUtils.Log(getClass().getSimpleName(), "vitamio download rate" + extra + "kb/s"); break; } return true; } // 捕获返回键的方法2 @Override public void onBackPressed() { if (requestCode == 200) { super.onBackPressed(); } else { viewShot(); } } /** * view截图 * * @return */ public void viewShot() { if (mediaPlayer != null) { // 核心代码start Bitmap bitmap = mediaPlayer.getCurrentFrame(); String savePath; if (bitmap != null) { String path = getExternalFilesDir(null).toString() + "/monitoringScreenshots/"; File file = new File(path); if (!file.exists()) { file.mkdirs(); } try { path = path + shotsName + ".png"; File file2 = new File(path); if (file2.exists()) { file2.delete(); } savePath = BitmapUtils.saveBitmapToFileWithFilePathWithOriginParams(path, bitmap).toString(); setResult(savePath); } catch (IOException e) { e.printStackTrace(); setResult(""); } } else { setResult(""); } } else { setResult(""); } } /** * savePath 截图保存地址 * * @param savePath */ private void setResult(String savePath) { Intent intent = new Intent(); intent.putExtra("screenshotPath", savePath); setResult(RESULT_OK, intent); finish(); } }

VideoView常用函数

/** * 获取扫描视频的Uri。 * 参数layout(缩放参数)参见MediaPlayer的常量:VIDEO_LAYOUT_ORIGIN(原始大小)、VIDEO_LAYOUT_SCALE(画面全屏)、VIDEO_LAYOUT_STRETCH(画面拉伸)、VIDEO_LAYOUT_ZOOM(画面裁剪)、VIDEO_LAYOUT_FIT_PARENT(画面铺满) * 参数aspectRation(宽高比),为0将自动检测 */ public void setVideoLayout(int layout,float aspectRatio); //Surface是否有效。 参见Surface的isValid方法。 public boolean isValid(); //设置视频路径。 public void setVideoPath(String path); //设置视频URI。(可以是网络视频地址) public void setVideoURI(Uri uri); //停止视频播放,并释放资源。 public void stopPlayback(); /** * 设置媒体控制器。 * 参数controller:媒体控制器,注意是io.vov.vitamio.widget.MediaController。 */ public void setMediaController(MediaController controller); //注册一个回调函数,在视频预处理完成后调用。在视频预处理完成后被调用。此时视频的宽度、高度、宽高比信息已经获取到,此时可调用seekTo让视频从指定位置开始播放。 public void setOnPreparedListener(OnPreparedListener l); //获取当前播放位置。 public long getCurrentPosition(); //设置播放位置。单位毫秒 public void seekTo(long msec); //是否正在播放。 public boolean isPlaying(); //获取缓冲百分比。 public int getBufferPercentage(); /** * 设置视频质量。 * 参数quality参见MediaPlayer的常量:VIDEOQUALITY_LOW(流畅)、VIDEOQUALITY_MEDIUM(普通)、VIDEOQUALITY_HIGH(高质) */ public void setVideoQuality(int quality); //设置视频缓冲大小。默认1024KB,单位byte public void setBufferSize(int bufSize); //检测是否缓冲完毕。 public boolean isBuffering(); //设置元数据编码。例如:UTF-8 public void setMetaEncoding(String encoding); 第三种:ExoPlayer

ExoPlayer是构建在Android低水平媒体API之上的一个应用层媒体播放器。和Android内置的媒体播放器相比,ExoPlayer有许多优点。ExoPlayer支持内置的媒体播放器支持的所有格式外加自适应格式DASH和SmoothStreaming。ExoPlayer可以被高度定制和扩展以适应不同的使用场景。

优点:

支持HTTP上的动态自适应流DASH和SmoothStreaming。更多详情请参看 Supported formats。支持高级的HLS特点,例如正确的处理#EXT-X-DISCONTINUITY标签。能够无缝的合并,串联,循环播放媒体文件。能够被高度扩展和定制,以适用不同的场景。加载速度快支持视频截图支持自定义组件

缺点:

在某些设备上播放音频,ExoPlayer可能会比MediaPlayer消耗更多的电量。

ExoPlayer gitHub 地址:https://github.com/google/ExoPlayer/

集成方式:

implementation 'com.google.android.exoplayer:exoplayer:2.13.2'

使用:

class ExoplayerActivity : Activity(), Player.EventListener { var path: String? = null private var simpleExoPlayer: SimpleExoPlayer? = null private var requestCode = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) requestWindowFeature(Window.FEATURE_NO_TITLE) window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) setContentView(R.layout.activity_exoplayer) path = intent.getStringExtra("ADDRESS") requestCode = intent.getIntExtra("requestCode", 200) initView() } private fun initView() { simpleExoPlayer = SimpleExoPlayer.Builder(this).build() path?.let { val mediaItem: MediaItem = MediaItem.fromUri(it) simpleExoPlayer?.setMediaItem(mediaItem) simpleExoPlayer?.prepare() } simpleExoPlayer?.addListener(this) exoplayer_videoView.player = simpleExoPlayer findViewById(R.id.exoplayer_back).setOnClickListener { onBackPressed() } } override fun onPlaybackStateChanged(state: Int) { // Player.STATE_IDLE -> "ExoPlayer.闲置状态 -" // Player.STATE_BUFFERING -> "ExoPlayer.国家缓冲 -" // Player.STATE_READY -> "ExoPlayer.准备就绪 -" // Player.STATE_ENDED -> "ExoPlayer.状态已结束 -" // else -> "UNKNOWN_STATE when (state) { Player.STATE_READY -> { exoplayer_progressBar.visibility = View.GONE } else -> { } } } override fun onResume() { super.onResume() simpleExoPlayer?.play() } override fun onStop() { super.onStop() simpleExoPlayer?.pause() } override fun onDestroy() { super.onDestroy() simpleExoPlayer?.removeListener(this) simpleExoPlayer?.release() } // 捕获返回键的方法2 @Override override fun onBackPressed() { if (requestCode == 200) { super.onBackPressed() } else { saveBitmap() } } /** * 获取并保存截图 */ private fun saveBitmap() { try { val textureView: TextureView = exoplayer_videoView.videoSurfaceView as TextureView val screenshotBitmap = textureView.bitmap val path = getExternalFilesDir(null).toString() + "/monitoringScreenshots/" val file = File(path) if (!file.exists()) { file.mkdirs() } //文件名为时间 val timeStamp = System.currentTimeMillis() val sdf = SimpleDateFormat("yyyyMMdd_HHmmss") val sd = sdf.format(Date(timeStamp)) val fileName = "$sd.jpg" val savePath = path + fileName val outputStream = FileOutputStream(savePath) screenshotBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) outputStream.flush() outputStream.close() setResult(savePath) } catch (e: IOException) { e.printStackTrace() finish() } } /** * savePath 截图保存地址 * * @param savePath */ private fun setResult(savePath: String) { val intent = Intent() intent.putExtra("screenshotPath", savePath) setResult(RESULT_OK, intent) finish() } }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3