由于录像之后,原先选用的腾讯VOD点播播放器显示出来竖屏都变横屏了,虽然选中了现在的腾讯VOD点播,还是把Android视频播放器了解了一番。

Android自定义视频播放器有以下三种:

一、MediaPlayer与SurfaceView相结合

// 为SurfaceHolder添加回调
mSurfaceView.getHolder().addCallback(callback);
// 4.0版本之下需要设置的属性
// 设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到界面
mSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// SurfaceHolder回调
private SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {// SurfaceHolder被修改的时候回调@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {Log.i(TAG, "SurfaceHolder 被销毁");// 销毁SurfaceHolder的时候记录当前的播放位置并停止播放if (mediaPlayer != null && mediaPlayer.isPlaying()) {currentPosition = mediaPlayer.getCurrentPosition();mediaPlayer.stop();}}@Overridepublic void surfaceCreated(SurfaceHolder holder) {Log.i(TAG, "SurfaceHolder 被创建");if (currentPosition > 0) {// 创建SurfaceHolder的时候,如果存在上次播放的位置,则按照上次播放位置进行播放play(currentPosition);currentPosition = 0;}}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {Log.i(TAG, "SurfaceHolder 大小被改变");}};
// 播放本地视频
private void showLocalVideo(final int msec) {Log.i(TAG, " 获取视频文件地址");String path = mPath.getText().toString().trim();File file = new File(path);if (!file.exists()) {Toast.makeText(this, "视频文件路径错误", Toast.LENGTH_SHORT).show();return;}Log.i(TAG, "指定视频源路径");try {mediaPlayer = new MediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);// 设置播放的视频源mediaPlayer.setDataSource(file.getAbsolutePath());// 设置显示视频的SurfaceHoldermediaPlayer.setDisplay(mSurfaceView.getHolder());Log.i(TAG, "开始装载");mediaPlayer.prepareAsync();mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {Log.i(TAG, "装载完成");mediaPlayer.start();// 按照初始位置播放mediaPlayer.seekTo(msec);// 设置进度条的最大进度为视频流的最大播放时长mSeekBar.setMax(mediaPlayer.getDuration());// 开始线程,更新进度条的刻度new Thread() {@Overridepublic void run() {try {isPlaying = true;while (isPlaying) {// 如果正在播放,没0.5.毫秒更新一次进度条int current = mediaPlayer.getCurrentPosition();mSeekBar.setProgress(current);sleep(500);}} catch (Exception e) {e.printStackTrace();}}}.start();mBtnPlay.setEnabled(false);}});mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {// 在播放完毕被回调mBtnPlay.setEnabled(true);}});mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {// 发生错误重新播放play(0);isPlaying = false;return false;}});} catch (Exception e) {e.printStackTrace();}
}
// 播放远程视频
private void showRemoteVideo(final int msec) {String videoUrl2 = "http://edu.ismartwork.cn/v/lcl/ftpFile/data/workerCircle/cf7cc922de584d0f88833c13cd5fedf2/Screenrecord-2018-07-28-14-53-15-925.mp4";Uri uri = Uri.parse(videoUrl2);try {mediaPlayer = new MediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);// 设置播放的视频源mediaPlayer.setDataSource(this, uri);// 设置显示视频的SurfaceHoldermediaPlayer.setDisplay(mSurfaceView.getHolder());Log.i(TAG, "开始装载");mediaPlayer.prepareAsync();mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {Log.i(TAG, "装载完成");mediaPlayer.start();// 按照初始位置播放mediaPlayer.seekTo(msec);// 设置进度条的最大进度为视频流的最大播放时长mSeekBar.setMax(mediaPlayer.getDuration());// 开始线程,更新进度条的刻度new Thread() {@Overridepublic void run() {try {isPlaying = true;while (isPlaying) {// 如果正在播放,没0.5.毫秒更新一次进度条int current = mediaPlayer.getCurrentPosition();mSeekBar.setProgress(current);sleep(500);}} catch (Exception e) {e.printStackTrace();}}}.start();mBtnPlay.setEnabled(false);}});mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {mp.release();mp = null;// 在播放完毕被回调mBtnPlay.setEnabled(true);}});mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {// 发生错误重新播放play(0);isPlaying = false;return false;}});} catch (Exception e) {e.printStackTrace();}
}

常用SeekBar显示进度及改变进度

private SeekBar.OnSeekBarChangeListener change = new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {// 当进度条停止修改的时候触发// 取得当前进度条的刻度int progress = seekBar.getProgress();if (mediaPlayer != null && mediaPlayer.isPlaying()) {// 设置当前播放的位置mediaPlayer.seekTo(progress);}}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {}
};

二、基于MediaPlayer和SurfaceView,Android还提供了VideoView和MediaController,方便用户使用。VideoView继承了SurfaceView,并且实现了MediaPlayerControl接口,其内成员变量MediaPlayer

public class ControllerActivity extends Activity {private VideoView vv_video;private MediaController mController;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_controller);vv_video = (VideoView) findViewById(R.id.vv_video);// 实例化MediaControllermController = new MediaController(this);showRemoteVideo();}private void showLocalVideo() {File file = new File("/storage/emulated/0/DCIM/Camera/VID_20180731_081115.mp4");if (file.exists()) {// 设置播放视频源的路径vv_video.setVideoPath(file.getAbsolutePath());// 为VideoView指定MediaControllervv_video.setMediaController(mController);// 为MediaController指定控制的VideoViewmController.setMediaPlayer(vv_video);}}private void showRemoteVideo() {String videoUrl2 = "http://edu.ismartwork.cn/v/lcl/ftpFile/data/workerCircle/cf7cc922de584d0f88833c13cd5fedf2/Screenrecord-2018-07-28-14-53-15-925.mp4";Uri uri = Uri.parse(videoUrl2);vv_video.setVideoURI(uri);vv_video.setMediaController(mController);vv_video.start();}
}

三、基于ExoPlayer

github:https://github.com/google/ExoPlayer

官网:http://google.github.io/ExoPlayer/

ExoPlayer是谷歌开源的专用于Android的应用级的多媒体播放器。Android框架提供的除MediaPlayer之外,还提供了些低等级的媒体API,例如:MediaCodec, AudioTrack,MediaDrm,可以用于建立自定义媒体播放。ExoPlayer支持一些Android自带的MediaPlayer现不支持的API,包括DASH(动态的自适应流HTTP)和SmoothStreaming adaptive playbacks(平滑流)。

翻译水平一般,直接上他人译文,

以下转自:https://www.jianshu.com/p/3251a5189f56及https://blog.csdn.net/u014606081/article/details/76181049

优点:

  • 支持通过HTTP(DASH)和SmoothStreaming进行动态自适应流,这两种都不受MediaPlayer的支持,还支持许多其他格式。
  • 能够自定义和扩展播放器,以适应各种不同需求。ExoPlayer专门设计了这一点,大部分组建都可以自己替换
  • 官网说了很多,其实说到底最主要的就是各个组件可以自定义,还可以接入ffmpeg组件,基本能满足99.9%的需求

缺点:ExoPlayer的音频和视频组件依赖Android的MediaCodec接口,该接口发布于Android4.1(API等级16),因此不得低于4.1

Library概述:

ExoPlayer库的核心是ExoPlayer接口,ExoPlayer公开了传统的高级媒体播放器功能,例如缓冲媒体,播放,暂停和seek等功能。在具体实现方面,该开源库对播放的媒体类型、存储方式、位置、渲染方式等进行了最少的实现,旨在让开发者自定义各种特性。ExoPlayer实现不是直接实现加载和呈现媒体,而是将这项工作委托给各种组件。 所有ExoPlayer共同的组件有:

  • MediaSource:定义多媒体数据源,这个类的功能就是从Uri中读取多媒体文件的二进制数据。 MediaSource在播放开始时通过ExoPlayer.prepare注入

  • TrackSelector:轨道提取器,从MediaSource中提取各个轨道的二进制数据,交给Renderer渲染。创建播放器时初注入

  • Renderer:对多媒体中的各个轨道(音轨、视频轨、字幕轨等)数据进行渲染,渲染就是“播放”,把二进制文件渲染成声音、画面。 创建播放器时注入

  • LoadControl:对MediaSource进行控制,比如什么时候开始缓冲、缓冲多少等等。创建播放器时注入

该库为提供了这些组件的默认实现,能够满足大部分需求,如果有特殊需求,可以通过自定义组件来实现。 比如,可以自定义LoadControl来更改播放器的缓冲策略,或自定义Renderer来渲染Android本身不支持的编解码器。

整个库中都存在注入组件的概念,许多子组件都能单独替换成自定义组件,而不会影响整个流程。 例如,默认的MediaSource实现需要通过其构造函数注入一个或多个DataSource工厂, 通过提供自定义Factory,可以从非标准源或不同的网络堆栈加载数据。

public class SimpleExoPlayerActivity extends Activity {private SimpleExoPlayer player;private PlayerView playerView;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_exoplayer);playerView = findViewById(R.id.playerView);// 1. Create a default TrackSelectorHandler mainHandler = new Handler();BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();TrackSelection.Factory videoTrackSelectionFactory =new AdaptiveTrackSelection.Factory(bandwidthMeter);DefaultTrackSelector trackSelector =new DefaultTrackSelector(videoTrackSelectionFactory);// 2. Create the playerplayer = ExoPlayerFactory.newSimpleInstance(this, trackSelector);playerView.setPlayer(player);// Measures bandwidth during playback. Can be null if not required.// DefaultBandwidthMeter bandwidthMeter1 = new DefaultBandwidthMeter();// Produces DataSource instances through which media data is loaded.DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,Util.getUserAgent(this, "yourApplicationName"));// This is the MediaSource representing the media to be played.String videoUrl2 = "http://edu.ismartwork.cn/v/lcl/ftpFile/data/workerCircle/cf7cc922de584d0f88833c13cd5fedf2/Screenrecord-2018-07-28-14-53-15-925.mp4";Uri uri = Uri.parse(videoUrl2);MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);// Prepare the player with the source.player.prepare(videoSource);player.setPlayWhenReady(true);}@Overrideprotected void onPause() {super.onPause();if (player != null) {player.release();}}
}

四、基于FFmpeg

官网:http://ffmpeg.org/

FFmpeg有很多个平台,需要用户进行编译,这里就直接介绍一下ijkPlayer,ijkPlayer是B站开源的基于FFmpeg的ffplay。

github:https://github.com/Bilibili/ijkplayer

。。。未完待续。。。

注:

1. TecentExoPlayer

原腾讯VOD点播框架基于一代的ExoPlayer,自定义TecentExoPlayer,实现了EventListener

com.google.android.exoplayer.MediaCodecVideoTrackRenderer.EventListener
public interface EventListener extends com.google.android.exoplayer.MediaCodecTrackRenderer.EventListener {void onDroppedFrames(int var1, long var2);void onVideoSizeChanged(int var1, int var2, float var3);void onDrawnToSurface(Surface var1);
}

1). onVideoSizeChanged方法,float参数传递了一个宽高比,通过这个宽高比,知道视频本身是竖屏还是横屏的,但是我们上传的视频本身确实width和height是反的呀,改这里没法解决。。。

2). 注意到TencentExoPlayer中有个接口是InfoListener,其中方法onVideoFormatEnabled

public interface InfoListener {void onVideoFormatEnabled(Format var1, int var2, int var3);void onAudioFormatEnabled(Format var1, int var2, int var3);void onDroppedFrames(int var1, long var2);void onBandwidthSample(int var1, long var2, long var4);void onLoadStarted(int var1, long var2, int var4, int var5, Format var6, int var7, int var8);void onLoadCompleted(int var1, long var2, int var4, int var5, Format var6, int var7, int var8, long var9, long var11);void onDecoderInitialized(String var1, long var2, long var4);void onSeekRangeChanged(TimeRange var1);
}

想通过这种方式应该能获取到Format中旋转角度参数,而VideoRootFrame中设置所有的实现都是通过EventLogger

VideoRootFrame:

this.eventLogger = new EventLogger();
this.eventLogger.startSession();
this.player.addListener(this.eventLogger);
this.player.setInfoListener(this.eventLogger);
this.player.setInternalErrorListener(this.eventLogger);

EventLogger:

public void onVideoFormatEnabled(Format format, int trigger, int mediaTimeMs) {Log.d("EventLogger", "videoFormat [" + this.getSessionTimeString() + ", " + format.id + ", " + Integer.toString(trigger) + "]");
}

运行,试试Format中的值是否含旋转信息:

08-21 16:51:04.776 4692-4692/com.ygsoft.study.ygedu D/EventLogger: start [0]
08-21 16:51:04.778 4692-4692/com.ygsoft.study.ygedu D/EventLogger: state [0.00, false, P]
08-21 16:51:04.787 4692-4692/com.ygsoft.study.ygedu D/EventLogger: state [0.01, true, I]
08-21 16:51:04.853 4692-4692/com.ygsoft.study.ygedu D/EventLogger: state [0.08, true, P]
08-21 16:51:06.954 4692-4692/com.ygsoft.study.ygedu D/EventLogger: state [2.18, true, B]
08-21 16:51:07.226 4692-4692/com.ygsoft.study.ygedu D/EventLogger: decoderInitialized [2.45, OMX.qcom.video.decoder.avc]
08-21 16:51:07.264 4692-4692/com.ygsoft.study.ygedu D/EventLogger: decoderInitialized [2.49, OMX.google.aac.decoder]
08-21 16:51:07.321 4692-4692/com.ygsoft.study.ygedu D/EventLogger: videoSizeChanged [362, 640, 1.0]
08-21 16:51:07.361 4692-4692/com.ygsoft.study.ygedu D/EventLogger: state [2.58, true, R]

白高兴一场了,根本没执行:-(,找到TencentExoPlayer:

public void onDownstreamFormatChanged(int sourceId, Format format, int trigger, int mediaTimeMs) {if(this.infoListener != null) {if(sourceId == 0) {this.videoFormat = format;this.infoListener.onVideoFormatEnabled(format, trigger, mediaTimeMs);} else if(sourceId == 1) {this.infoListener.onAudioFormatEnabled(format, trigger, mediaTimeMs);}}
}

找了一通源码,只有HlsSampleSource中有调用:

private void notifyDownstreamFormatChanged(final Format format, final int trigger, final long positionUs) {if(this.eventHandler != null && this.eventListener != null) {this.eventHandler.post(new Runnable() {public void run() {HlsSampleSource.this.eventListener.onDownstreamFormatChanged(HlsSampleSource.this.eventSourceId, format, trigger, HlsSampleSource.this.usToMs(positionUs));}});}}

而VideoRootFrame中定义:

private RendererBuilder getRendererBuilder() {String userAgent = Util.getUserAgent(this.context, "ExoPlayerDemo");switch($SWITCH_TABLE$com$tencent$qcload$playersdk$util$VideoInfo$VideoType()[this.contentType.ordinal()]) {case 1:return new HlsRendererBuilder(this.context, userAgent, this.contentUri.toString(), this.audioCapabilities);case 2:return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new Mp4Extractor());case 3:return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new Mp3Extractor());case 4:return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new AdtsExtractor());case 5:return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new FragmentedMp4Extractor());case 6:case 7:return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new WebmExtractor());case 8:return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new TsExtractor(0L, this.audioCapabilities));default:throw new IllegalStateException("Unsupported type: " + this.contentType);}
}

我们这里是MP4,用的是ExtractorRendererBuilder,没有调用HlsRenderBuilder,所以没有执行,直接改实现,又担心其他格式的视频播放出问题,就没有继续了。。。

2. SimpleExoPlayer

SimpleExoPlayer,二代的ExoPlayer实现,估计那时候ExoPlayer还没有更新到2.x版本,SimpleExoPlayer实现了VideoListener,里面onVideoSizeChanged方法含能够将unappliedRotationDegrees

/** A listener for metadata corresponding to video being rendered. */
public interface VideoListener {/*** Called each time there's a change in the size of the video being rendered.** @param width The video width in pixels.* @param height The video height in pixels.* @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise*     rotation in degrees that the application should apply for the video for it to be rendered*     in the correct orientation. This value will always be zero on API levels 21 and above,*     since the renderer will apply all necessary rotations internally. On earlier API levels*     this is not possible. Applications that use {@link android.view.TextureView} can apply the*     rotation by calling {@link android.view.TextureView#setTransform}. Applications that do not*     expect to encounter rotated videos can safely ignore this parameter.* @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of*     square pixels this will be equal to 1.0. Different values are indicative of anamorphic*     content.*/void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio);/*** Called when a frame is rendered for the first time since setting the surface, and when a frame* is rendered for the first time since a video track was selected.*/void onRenderedFirstFrame();
}

另外SimpleExoPlayer中成员变量有个videoFormat,Format类型,含rotationDegrees属性,源码是这样注释的:

/*** The clockwise rotation that should be applied to the video for it to be rendered in the correct* orientation, or 0 if unknown or not applicable. Only 0, 90, 180 and 270 are supported.*/
public final int rotationDegrees;

所以利用SimpleExoPlayer和PlayerView是可以简单解决之前录像视频旋转的问题,但是这又没缓存,非常耗流量,界面也不太好看,赶时间,就选用了现腾讯VOD点播的API

3. 现腾讯云点播API,基于ffmpeg

Android 自定义视频播放器相关推荐

  1. Android自定义视频播放器(三)

    参看:Android自定义视频播放器(一):https://blog.csdn.net/zxd1435513775/article/details/81507909 参看:Android自定义视频播放 ...

  2. Android自定义视频播放器(一)

    一.引言 我们在开发Android多媒体应用时,有两种方式来播放多媒体资源.第一种是使用隐式的Intent,来使用系统或者手机已经安装的第三方播放器应用来播放音视频,第二种是使用Android自带的. ...

  3. Android 自定义视频播放器,可用于以视频做背景的需求

    话不多说,直接上代码: public class BgView extends TextureView implements TextureView.SurfaceTextureListener {p ...

  4. Android进阶:自定义视频播放器开发(下)

    上一篇文章我们主要讲了视频播放器开发之前需要准备的一个知识,TextureView,用于对图像流的处理.这篇文章开始构建一个基础的视频播放器. 一.准备工作 在之前的文章已经说过了,播放器也是一个vi ...

  5. Android开发笔记(一百二十五)自定义视频播放器

    视频播放方式 在Android中播放视频的方式有两种: 1.使用MediaPlayer结合SurfaceView进行播放.其中通过SurfaceView显示视频的画面,通过MediaPlayer来设置 ...

  6. Android进阶:自定义视频播放器开发(上)

    随着快手,抖音,西瓜视频等视频APP的崛起,视频播放已经成为主流,此时作为Android研发的你,想要提高自己的能力还不知道怎么开发视频播放器怎么行?所以今天就带着大家一起开发一个简易播放器:Smal ...

  7. 《android多媒体api》之MediaPlayer自定义视频播放器

    <android多媒体api>系列是整合梳理android开发中经常用到的媒体相关api:多媒体开发主要内容有音频.视频录制播放.摄像头操作.录制操作.流媒体.直播.推流.拉流等方面:最近 ...

  8. Android系列之自定义视频播放器

    Android系列之自定义视频播放器 自述 xml部分 Java对面部分 需要使用到的Animation资源 需要自定义的工具类 自述 自己写的一个简易视频播放器,如果有需要的话欢迎参考和转载,但拒绝 ...

  9. 一步步自定义视频播放器——TextureView+MediaPlayer自定义视频播放器

    本篇参考封装一个视频播放器,原文已经写的非常棒了,本篇加入了个人对其内容的理解.秉承不重复造轮子的良好理念,接下来开始拆解轮子.内容非常多,我都差点放弃写,有耐心的请往下看 github上非常棒的视频 ...

最新文章

  1. python搭建博客系统_用Pelican快速搭建极简静态博客系统
  2. loadrunner-2-12日志解析
  3. python循环语句-python中的for循环语句怎么写
  4. DropdownList树
  5. Leetcode题库 2038.邻色同删(双指针法 C实现)
  6. 回溯算法之电话号码的字母组合
  7. Confluence 6 选项 1 – 在 Confluence 中手动重建用户和用户组
  8. 《First Order Motion Model for Image Animation》论文解读
  9. Webbench学习笔记二:getopt_long函数和build_request函数
  10. ijkplayer框架详解
  11. 158. class, static, self, parent
  12. python解析.he4文件
  13. Springboot
  14. win10远程计算机管理,Win10专业版远程控制电脑有哪些方法?
  15. Ethernet和802.3的区别及历史
  16. 【windows】Webstorm2021安装教程
  17. 解决Linux系统centos7的开机报错:Welcome to emergency mode
  18. 如何训练出专属的 OpenAI Five ?
  19. 前端大全(基础总结)(根据js权威指南扩展)
  20. 2018年11月总结,12月计划

热门文章

  1. 单片射频收发器nRF905及其应用
  2. 办公室空调节能集中控制方案
  3. 惠普系列笔记本爆出严重性能缺陷
  4. beta阶段第七次scrum meeting
  5. 互联网热门词汇:移动互联网最常出现的名词有哪些?
  6. 如何获取McAfee官网病毒库
  7. 3分钟让你轻松了解CRM 1
  8. 如何高速下载百度网盘文件?—第一篇—使用Aria2
  9. Unity Shader 视差映射
  10. 【mac】mac设置鼠标习惯