Android m3u8网络视频播放

article/2025/9/15 16:17:56

最近在做 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: optionalimplementation '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, experimentalimplementation '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 {@Overridepublic void surfaceCreated(SurfaceHolder holder) {}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {loadVideo();}@Overridepublic 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):布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:background="@color/color_000000"android:layout_height="match_parent"><LinearLayoutandroid:layout_width="match_parent"android:layout_centerInParent="true"android:layout_height="wrap_content"android:background="@color/color_000000"android:orientation="vertical"><com.tzyun.pip.view.IjkVideoViewandroid:id="@+id/jkVideoView"android:layout_width="match_parent"android:layout_height="300dp" /></LinearLayout><ImageViewandroid:id="@+id/back"android:layout_width="wrap_content"android:layout_height="48dp"android:contentDescription="@null"android:paddingStart="13dp"android:paddingEnd="13dp"android:src="@mipmap/back_video" /><ProgressBarandroid:id="@+id/progressBar"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true" /></RelativeLayout>

3):Activity

public class IjkPlayerVideoActivity extends Activity {private AlertDialog alertDialog;private String path;private IjkVideoView ijkVideoView;@Overridepublic 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() {@Overridepublic void onPrepared(IMediaPlayer mp) {//播放成功处理mp.start();findViewById(R.id.progressBar).setVisibility(View.INVISIBLE);}@Overridepublic void onCompletion(IMediaPlayer iMediaPlayer) {}@Overridepublic boolean onError(IMediaPlayer mp, int what, int extra) {ToastUtils.showToast("播放失败");finish();return true;}});//        path = "你自己的播放地址";//路径ijkVideoView.setPath(path);ijkVideoView.start();}@Overridepublic 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);}@Overrideprotected void onDestroy() {IjkMediaPlayer.native_profileEnd();if (alertDialog != null && alertDialog.isShowing()) {alertDialog.dismiss();}ijkVideoView.stop();super.onDestroy();}@Overrideprotected void onResume() {super.onResume();findViewById(R.id.progressBar).setVisibility(View.VISIBLE);if (!ijkVideoView.isPlaying()) {ijkVideoView.start();}}@Overrideprotected void onPause() {super.onPause();ijkVideoView.pause();}
}

第二种:Vitamio

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

新的功能:

  1. 支持大多数FFmpeg AVOptions,从而启用自定义HTTP标头支持。
  2. 支持更多的硬件,例如X86或MIPS。
  3. 改善流媒体,特别是支持自适应比特率流媒体,需要手动打开。
  4. 包含OpenSSL,因此支持某些与SSL相关的协议,例如https,tls,rtmps,rtmpts。
  5. 播放速度控制从0.5倍到2.0倍。
  6. 改进的字幕支持,包括外部位图字幕。
  7. 在线视频缓存到本地存储中,并且可以重复使用,直到删除缓存文件为止。
  8. 更多MediaPlayer API,例如getMetadata,getVideoTrack。
  9. 完整的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

<activityandroid:name="io.vov.vitamio.activity.InitActivity"android:configChanges="orientation|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"android:launchMode="singleTop"android:theme="@android:style/Theme.NoTitleBar"android:windowSoftInputMode="stateAlwaysHidden" />

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

添加权限:

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

至此,vitamio导入完毕.

5)、VideoView控件的使用

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><io.vov.vitamio.widget.CenterLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/color_000000"android:orientation="vertical"><io.vov.vitamio.widget.VideoViewandroid:id="@+id/buffer"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center" /></io.vov.vitamio.widget.CenterLayout><ImageViewandroid:id="@+id/back"android:layout_width="wrap_content"android:layout_height="48dp"android:contentDescription="@null"android:paddingStart="13dp"android:paddingEnd="13dp"android:src="@mipmap/back_video" /><ProgressBarandroid:id="@+id/progressBar"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true" /></RelativeLayout>
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;@Overridepublic 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.0mediaPlayer.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;});}@Overrideprotected void onDestroy() {if (alertDialog != null && alertDialog.isShowing())alertDialog.dismiss();super.onDestroy();}@Overrideprotected void onResume() {super.onResume();findViewById(R.id.progressBar).setVisibility(View.VISIBLE);}@Overridepublic 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@Overridepublic void onBackPressed() {if (requestCode == 200) {super.onBackPressed();} else {viewShot();}}/*** view截图** @return*/public void viewShot() {if (mediaPlayer != null) {// 核心代码startBitmap 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可以被高度定制和扩展以适应不同的使用场景。

优点:

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

缺点:

  1. 在某些设备上播放音频,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? = nullprivate var simpleExoPlayer: SimpleExoPlayer? = nullprivate var requestCode = 0override 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 = simpleExoPlayerfindViewById<View>(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_STATEwhen (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@Overrideoverride fun onBackPressed() {if (requestCode == 200) {super.onBackPressed()} else {saveBitmap()}}/*** 获取并保存截图*/private fun saveBitmap() {try {val textureView: TextureView = exoplayer_videoView.videoSurfaceView as TextureViewval screenshotBitmap = textureView.bitmapval 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 + fileNameval 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()}
}
<?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="@color/color_000000"tools:context="com.tzyun.pip.ijkPlayer.ExoplayerActivity"><com.google.android.exoplayer2.ui.PlayerViewandroid:id="@+id/exoplayer_videoView"android:layout_width="0dp"android:layout_height="0dp"app:auto_show="false"app:use_controller="false"app:surface_type="texture_view"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><ImageViewandroid:id="@+id/exoplayer_back"android:layout_width="wrap_content"android:layout_height="48dp"android:contentDescription="@null"android:paddingStart="13dp"android:paddingEnd="13dp"android:src="@mipmap/back_video"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /><ProgressBarandroid:id="@+id/exoplayer_progressBar"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

http://chatgpt.dhexx.cn/article/ZgnjKMro.shtml

相关文章

网页在线视频下载教程(m3u8格式介绍及下载教程)

简介&#xff1a; m3u8文件是苹果公司使用的HTTP Live Streaming&#xff08;HLS&#xff09;协议格式的基础。HLS是新一代流媒体传输协议&#xff0c;其基本实现原理为将一个大的媒体文件进行分片&#xff0c;将该分片文件资源路径记录与m3u8文件&#xff08;即playlist&…

什么是m3u8?

什么是m3u8? ​  m3u8是苹果公司推出的视频播放标准&#xff0c;是m3u8的一种&#xff0c;只是编码格式采用的是UTF-8。 m3u8准确来说是一种索引文件&#xff0c;使用m3u8文件实际上是通过它来解析对应的放在服务器上的视频网络地址&#xff0c;从而实现在线播放。使用m3u8…

M3U8是什么

m3u8是苹果公司推出的视频播放标准&#xff0c;是m3u8的一种&#xff0c;只是编码格式采用的是UTF-8。   m3u8准确来说是一种索引文件&#xff0c;使用m3u8文件实际上是通过它来解析对应的放在服务器上的视频网络地址&#xff0c;从而实现在线播放。使用m3u8格式文件主要因为…

video.js播放m3u8视频

m3u8 是一种基于HTTP Live Streaming&#xff08;HLS&#xff09;文件视频格式&#xff0c;它主要是存放整个视频的基本信息和分片(Segment)组成。目前 由 Apple.inc 率先提出的 HLS 协议在 Mac 的 Safari 上原生支持。 video.js是H5视频播放器&#xff0c;支持播放m3u8视频。这…

下载 .m3u8视频文件

简介 M3U8 是 Unicode 版本的 M3U&#xff0c;用 UTF-8 编码。"M3U" 和 "M3U8" 文件都是苹果公司使用的 HTTP Live Streaming&#xff08;HLS&#xff09; 协议格式的基础&#xff0c;这种协议格式可以在 iPhone 和 Macbook 等设备播放。 上述文字定义来自…

前端播放m3u8格式视频

m3u8是苹果公司推出的视频播放标准&#xff0c;是m3u的一种&#xff0c;只是编码格式采用的是UTF-8。m3u8准确来说是一种索引文件&#xff0c;使用m3u8文件实际上是通过它来解析对应的放在服务器上的视频网络地址&#xff0c;从而实现在线播放。 m3u8格式的视频是将文件分成一小…

使用videojs播放m3u8视频

vue3使用videojs 播放m3u8格式视频 videojs是一个播放视频的js库&#xff0c;可以通过videojs结合videojs-contrib-hls实播放m3u8格式视频。流媒体传输协议(hls)定义了用来控制播放的m3u8文件 m3u8是一个文本文件(播放列表文件)&#xff0c;里面的内容就是被播放的音视频文件路…

网页播放 .m3u8 视频文件

1,使用 dplayer,官网上有例子 <link href"https://cdn.bootcss.com/dplayer/1.25.0/DPlayer.min.css" rel"stylesheet"> <script src"https://cdn.jsdelivr.net/npm/hls.jslatest"></script> <script src"https://cd…

m3u8

1.什么是m3u8&#xff1f; 要想知道什么是m3u8最直接最粗暴的方式是找几个m3u8文件拔出来看看就知道。(话说是驴子是马出来溜溜就知道…) 下面我给出了2个m3u8连接&#xff1a; 1.http://cache.utovr.com/201508270528174780.m3u8 2.http://devimages.apple.com/iphone/sam…

M3U8在线MP4格式

MP4 格式是目前来说较为通用的格式一般的播放器都支持播放&#xff0c;兼容性十分友好。 不过可能会在网站在线播放的时候接触到 m3u8 文件&#xff0c;这种文件格式无法直接下载播放&#xff0c;如果想要在电脑上播放这种视频&#xff0c;则需要把 m3u8 文件转换成mp4格式。 介…

M3U8在线播放

M3U8在线播放 前言一、思路二、代码框架1. 移动端适配2. 改变M3U8地址3. 设置videojs参数4. 增加快进等功能 写在最后 前言 当我们在网上愉快观影的时候&#xff0c;难免会遇到“M3U8格式”的视频。聪明的你应该也发现了&#xff0c;它是没办法直接播放的。它其实只是一个索引…

M3U8文件简介及在线播放器

m3u8文件格式 M3U8是Unicode版本的M3U&#xff0c;用UTF-8编码。“M3U” 和 “M3U8” 文件都是苹果公司使用的 HTTP Live Streaming&#xff08;HLS&#xff09; 协议格式的基础&#xff0c;这种协议格式可以在 iPhone 和 Macbook 等设备播放。m3u8文件其实是 HTTP Live Strea…

m3u8 播放视频

使用m3u8 播放视频&#xff1a; m3u8在线播放 只需放视频链接即可 链接 http://tool.liumingye.cn/m3u8/index.php 下载 m3u8 js css 链接&#xff1a;https://pan.baidu.com/s/1dTAX_1B6hrF50O92a6GxuQ 提取码&#xff1a;yyds 引入到 vue 在index.html里面或者npm 下载 引…

正弦波SFR分辨率测试卡

除非生产商对生产的手机相机有特殊功能要求&#xff0c;否则将采用基于标准的相机手机图像质量评估工作&#xff0c;也就是分辨率测试卡的形式进行相机手机图像质量测评。早期的分辨率测试卡是ISO12233&#xff0c;也是国际针对图像质量一种评估标准。但是随着时代的变化相机评…

Android手机分辨率测试程序

该程序可以测试出个人手机设备的分辨率&#xff0c;属于哪种dpi级别&#xff0c;以便开发参考。 main.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android…

sfr测试图像清晰度 C 语言,SFR分辨率测试卡的不足和改进

分辨率测试卡使用SFR来测量手机相机清晰度主要源于它可以提供丰富的空间信息方面的潜力。当呈现SFR曲线(无论是从边缘还是正弦波)时&#xff0c;可能会有些令人费解&#xff0c;特别是当形状不是经典的低通功能时。这也可能是一些从业者放弃它的原因&#xff0c;有利于解释但不…

4k显卡视频测试软件,4K分辨率下体验测试

●4K分辨率桌面体验测试: 4K分辨率 1080P分辨率 所有桌面图标变为原来的1/4!可现实面积迅速增加,桌面可显示图标从原来的250个(25x10)变为1071个(51x20)!可显示的东西迅速增多。不过图标的变小使得鼠标非常不好点,这种情况下鼠标的DPI要足够的高,不然挪动距离会相当大,使…

web兼容性(分辨率)测试插件

web兼容性&#xff08;分辨率&#xff09;测试插件 Resolution Test插件 在谷歌浏览器扩展程序中下载即可 Resolution Test使用

软件测试常见分辨率测试,如何用imatest测分辨率 imatest软件测试分辨率图文教程...

imatest是什么软件&#xff1f;imatest怎么使用&#xff1f;imatest是一款专业的图像分析软件&#xff0c;具有强大的图像分析和处理功能&#xff0c;今天我就和大家聊一聊如何使用imatest软件测试分辨率。好了&#xff0c;话不多说&#xff0c;接下来就让我们一起去看看使用im…

测试-分辨率兼容测试

不努力&#xff0c;是会被合并同类项的。所以你要做那个被提取的公因式。 分辨率兼容测试 分辨率的定义 分辨率可以从显示分辨率与图像分辨率两个方向来分类。 显示分辨率(屏幕分辨率)是屏幕图像的精密度&#xff0c;是指显示器所能显示的像素有多少。由于屏幕上的点、线和面…