文章来自:http://blog.csdn.net/intbird 转载请说明出处

五一第一天在家休息(后续休息有空会进行升级)
看了一下视频播放的相关东西
写了一个简单的触摸视频播放器
(使用 MediaPlayer 和 ExoPlayer )
我喜欢从自己的使用感受做些调整
所以在里面有一个 触摸平滑进度 的实现
还有就是代码面向抽象,方便后续一些功能替换
有空的话我就会继续写写它

Github源码地址: https://github.com/intbird/VideoPlayerLib

GitHub issues(持续维护,待开发):
https://github.com/intbird/VideoPlayerLib/issues/2

目录

  • 基础功能:
  • 完成效果:
  • 结构解析:
  • 代码结构:
  • 1.项目结构
    • 1.api: 使用Fragment(推荐此方式)
    • 2.api: 使用Activity(模块解耦实现)
    • 3.总体结构
    • 4.资源前缀
    • 5.播放实现
    • 6.自动旋屏
  • 2. 大体思路
    • 1. 不同播放器不同布局, 统一接口多个回调
      • 1.面向接口, 抽象代码
      • 2.面向复杂, 抽象配置
    • 2. 动态修改播放参数,回传到播放器/界面等外部
      • 1. IntentHelper
      • 2. 实现代理方法
      • 3. 参数更新
      • 4. 处理更新
    • 3.将显示拆分为 视频显示 和 字幕显示等
      • 1. 视频显示
      • 2. 字幕显示
    • 4. touch里面的一些平滑实现
      • 1.明确需求
      • 2.触摸的范围检测:
      • 3.视差因子(提升滑动体验):
      • 4.提升触摸的UI体验(滑动平滑不生硬)
      • 5.滑动只计算数值,实现交给外部
  • 3. 部分实现
    • 1. activity入口代码
    • 2. 锁/解锁定当前屏幕方向
    • 3. 接口和实现
    • 4.触摸完整代码
    • 5.控制面板消失时加一些动画过渡,效果稍微好些
  • 4. 资源文件
    • 1. seekbar样式自定义:
    • 2. 水波纹效果的版本兼容
    • 3. 格式问题
    • 4. 沉浸式状态栏

基础功能:

播放不同的 分辨率/字幕/倍速/初始进度
在屏幕中间双击: 切换播放/暂停
在屏幕中间滑动: 拖动进度
在屏幕左侧滑动: 控制亮度
在屏幕右侧滑动: 控制声音
点击锁定按钮: 锁定当前所有操作
点击上一个/下一个/播放/暂定/停止: 执行对应动作
横竖屏方向自动切换

有时间的话会继续开发......

完成效果:

  1. 自动旋屏: https://blog.csdn.net/intbird/article/details/109077762

  1. 部分截图:




Github源码地址: https://github.com/intbird/VideoPlayerLib
文章来自:http://blog.csdn.net/intbird 转载请说明出处

结构解析:

0.权限检查: 读取媒体权限
1.播放器层: 播放器的接口 + 实现
2.触控层: 触摸区域识别 + 手势识别 + 触摸灵敏度和进度反馈
3.控制面板层: 可视UI按钮(上一个,下一个,播放/暂停/停止)
4.锁定层: 锁(锁播放器+ 锁触控 + 锁面板+ 锁屏幕方向等)

代码结构:

用mvc简单实现一下,有空了可以把view这层在做层封装,方便后续更换UI的最小代价

1.项目结构

1.api: 使用Fragment(推荐此方式)
 VideoPlayerFragment.newInstance(array, index, style, autoPlay)

2.api: 使用Activity(模块解耦实现)
 ServicesLoader.load(IVideoPlayer::class.java)?.startActivity(this,array, index, autoPlay)

显示声明外部api和实现默认的api

3.总体结构

逐步升级, 代码结构有些许调整

4.资源前缀

5.播放实现

  1. MediaPlayerImpl
 intbird.soft.lib.video.player.main.player.player.MediaPlayerImpl
  1. ExoPlayerImpl(有空会继续实现)
 intbird.soft.lib.video.player.main.player.player.ExoPlayerImpl
6.自动旋屏

自动旋转视频和抖动恢复屏幕方向 : https://blog.csdn.net/intbird/article/details/109077762

2. 大体思路

1. 不同播放器不同布局, 统一接口多个回调
1.面向接口, 抽象代码
    private var playerCall: PlayerCallbacks? = nullprivate var playerView: MediaViewInfo<out View, out View>? = nullprivate var player: IPlayer? = nullprivate fun instanceMediaPlayer(mediaPlayerType: MediaPlayerType?) {when(mediaPlayerType) {MediaPlayerType.PLAYER_STYLE_1, MediaPlayerType.PLAYER_STYLE_2 -> {playerCall = PlayerCallbacks(playerCallback, videoPlayerCallback)playerView = MediaViewProvider(view).by(mediaPlayerType)player = MediaPlayerImpl(getInternalActivity(), playerView?.display as? TextureView, subtitleText, intentHelper, playerCall)}MediaPlayerType.PLAYER_STYLE_3 -> {playerCall = PlayerCallbacks(playerCallback, videoPlayerCallback)playerView = MediaViewProvider(view).by(mediaPlayerType)player = ExoPlayerImpl(getInternalActivity(), playerView?.display as? TextureView, playerCall)}}}
2.面向复杂, 抽象配置

通过一个UI的配置项,强制让不同类型的播放自己选择如何和界面关联

enum class MediaPlayerType(open val layoutDisplay: Int,open val viewDisplay: Int,open val layoutControl: Int,open val viewControl: Int
) {PLAYER_STYLE_1(R.layout.lib_media_player_diaplay_texture,R.id.textureView,R.layout.lib_media_player_control_style_1,R.id.mediaRootControl),PLAYER_STYLE_2(R.layout.lib_media_player_diaplay_texture,R.id.textureView,R.layout.lib_media_player_control_style_2,R.id.mediaRootControl),PLAYER_STYLE_3(R.layout.lib_media_player_diaplay_ijkplayerR.id.ijkplayer,R.layout.lib_media_player_control_style_2,R.id.mediaRootControl)
}
2. 动态修改播放参数,回传到播放器/界面等外部
1. IntentHelper

IntentHelper: 获取外部传入的Intent列表 的 某个Index
进而进行组装当前播放的数据
但:我想如果一个player实现类,他的数据不是外部传入的
那么:他也可以替换掉这个IntentHelper,在少量修改后要
能将 切换播放/上一首/下一首 等改为自己的内部方法

始终根据index获取数据

2. 实现代理方法

3. 参数更新

MediaItemInfoRecord: 通过它来保存播放状态,
以便在切换分辨率,字幕等需要强行进行状态改变后
继续操作前的进度进行播放

onReceivePlayFile: 这个在收到播放参数变更时会通知到播放器/控制层等等

4. 处理更新
override fun onReceivePlayFile(reload: Boolean, mediaFileInfo: MediaFileInfo?) {if (null == mediaFileInfo) returnif (reload) {player?.prepare(mediaFileInfo)} else {player?.onParamsChange(mediaFileInfo)}videoControlController?.onParamsChange(mediaFileInfo)}
3.将显示拆分为 视频显示 和 字幕显示等
1. 视频显示

MediaPlayer用surface作为显示器
: 如果用第三方播放器,显示可能为其他控件,比如一个webview播放器
所以: 则要预留出接口, 方便替换

class TextureDisplay(private val iDisplay: IDisplay) : TextureView.SurfaceTextureListener {override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?,width: Int,height: Int) {}override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {}override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {return true}override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {iDisplay.displayStateChange(true)}
}
2. 字幕显示

MediaPlayer添加字幕为TimedText或者Subtitle
: 如果自己解析的话,则可能是另外一种实现方式
所以: 将字幕显示操作绑定到不同的实现方式上,方便后续替换
可以: 从github找一个字幕实现库进行替换实现

4. touch里面的一些平滑实现
1.明确需求

1.左侧滑动控制亮度
可调节值: 调节系统亮度值(-1.0 -1.0) 和 调节当前窗口(-1.0 - 1.0)
注意这里是( -1.0 - 1.0 ),UI进度一般为(0-100)不会有负数, 需要处理
这里有个问题,系统标示-1为不可用, 但调节时 0 是最小值,1是最大值
WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF
WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL

2.右侧音量调节
可调节值: 调节系统声音值(0-15)
注意这里是(0-15),如果0-1进度需要滑动过长,则进度笔记生硬

3.进度的百分比实时拖动时注意卡顿情况
1.如果需要实时预览要保证不卡顿(一些机器有些卡)
2.如果不实时预览,则需要考虑如何实现

2.触摸的范围检测:
 private var allowXAlixRange: Rect? = nullprivate var allowYAlixRangeLeft: Rect? = nullprivate var allowYAlixRangeRight: Rect? = null
3.视差因子(提升滑动体验):

比如滑动多长距离才能对应1个音量或者1个进度的一个百分比

        // 进度视差因子private val parallaxX = 1f// 音量视差因子private val parallaxYVolume = 4.4f// 亮度视差因子private val parallaxYLight = 4.4f
4.提升触摸的UI体验(滑动平滑不生硬)

1.比如音量是0-15,太长的屏幕滑动起来感觉不柔和,一次跳跃的距离有些长
2.亮度是( -1.0 - 1.0 ), UI进度一般为(0-100)不会有负数
所以也要转正( 0 - minValue) 并且 放大处理 (actuary = 100)
转正的意思是将 -1.0 - 1.0 变为 0.0 - 2.0,然后进行正数的UI放大100倍
这里有个问题,系统标示-1为不可用, 但调节时 0 是最小值,1是最大值
WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF
WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL

3.综上: AdjustInfo 对象内部参数调整为
音量UI:( 0 - 15 ) -> ( 0 - 1500 )
亮度UI: ( -1.0 - 1.0 ) -> (0 - 200 )
AdjustInfo:

private fun realValue() {....
}private fun absUIValue() {val actuary = 100if (minValue < 0) {val diff = 0 - minValue...currentValueUI += ((currentValue + diff) * actuary).toInt()} else {...currentValueUI += (currentValue * actuary).toInt()}}/*** 进度变更时,也可以监听实际值 去放大 UI值* 这里使用了直接赋两个值(实际值和UI值),简单一些**/fun addIncrease(increaseRatio: Float) {progress = MediaTimeUtil.adjustValueBoundF((currentValue + increaseRatio * maxValue), maxValue, minValue)progressUI = MediaTimeUtil.adjustValueBoundF((currentValueUI + (increaseRatio * maxValueUI)), maxValueUI.toFloat(), minValueUI.toFloat()).toInt()}
5.滑动只计算数值,实现交给外部

音量/亮度如何调节实现交给外部实现
后面时间多些了可以把view层也做一层抽离,目前问题也不大

3. 部分实现

1. activity入口代码
class VideoPlayerActivity : Activity(), ILockExecute {companion object {var EXTRA_PLAYER_TYPE = "videoType"var EXTRA_FILE_URLS = "videoUrls"var EXTRA_FILE_INDEX = "videoIndex"var EXTRA_PLAYER_AUTO_PLAY = "videoAutoPlay"fun newInstance(playList: ArrayList<MediaPlayItem>?, playIndex: Int, playerType: MediaPlayerType, autoPlay:Boolean): VideoPlayerFragment {val fragment = VideoPlayerFragment()var args = fragment.argumentsif (null == args) args = Bundle()args.putParcelableArrayList(EXTRA_FILE_URLS, playList)args.putInt(EXTRA_FILE_INDEX, playIndex)args.putSerializable(EXTRA_PLAYER_TYPE, playerType)args.putBoolean(EXTRA_PLAYER_AUTO_PLAY, autoPlay)fragment.arguments = argsreturn fragment}}![在这里插入图片描述](https://img-blog.csdnimg.cn/20200922162840799.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ludGJpcmQ=,size_16,color_FFFFFF,t_70#pic_center)...private var player: IPlayer? = nullprivate var locker: LockController? = nullprivate var videoTouchController: TouchController? = nullprivate var videoControlController: ControlController? = nulloverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)intentHelper = MediaIntentHelper(arguments, intentCallback)instanceMediaPlayer(intentHelper?.mediaPlayerType)locker = LockController(ivPopLock)videoTouchController = TouchController(player, locker, touchCallback, layoutTouchPanel)videoControlController = ControlController(player, locker, controlCallback, playerView?.control)locker?.addExecute(videoTouchController)?.addExecute(videoControlController)?.addExecute(this)executeLock(false)handBackPressed()}private fun instanceMediaPlayer(mediaPlayerType: MediaPlayerType?) {when(mediaPlayerType) {MediaPlayerType.PLAYER_STYLE_1, MediaPlayerType.PLAYER_STYLE_2 -> {playerCall = PlayerCallbacks(playerCallback, videoPlayerCallback)playerView = MediaViewProvider(view).by(mediaPlayerType)player = MediaPlayerImpl(getInternalActivity(), playerView?.display as? TextureView, subtitleText, intentHelper, playerCall)}MediaPlayerType.PLAYER_STYLE_3 -> {playerCall = PlayerCallbacks(playerCallback, videoPlayerCallback)playerView = MediaViewProvider(view).by(mediaPlayerType)player = ExoPlayerImpl(getInternalActivity(), playerView?.display as? TextureView, playerCall)}}}
2. 锁/解锁定当前屏幕方向

这里有个待实现的是监听OrientationEventListener,
类似iPad抖动一下屏幕恢复和手机一致的方向

    private fun calScreenOrientation(activity: Activity): Int {val display = activity.windowManager.defaultDisplayreturn when (display.rotation) {// 横屏Surface.ROTATION_90, Surface.ROTATION_270 -> {ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE}else -> {ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT}}}override fun executeLock(lock: Boolean) {// 当前方向val orientation:Int = calScreenOrientation(this)// 方向锁定if (lock) {if (this.requestedOrientation != orientation) {this.requestedOrientation = orientation}} else {this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR}// 是否禁用自动转屏if (MediaLightUtils.checkSystemWritePermission(this))Settings.System.putInt(contentResolver, Settings.System.ACCELEROMETER_ROTATION, if (lock) 0 else 1)}
3. 接口和实现

播放器的接口,滑动接口,触摸等接口和 对应的实现

interface IPlayer {/*** 实际上这个通知由display调用,这里先简化一下*/fun available(display: Surface?)fun prepare(mediaFileInfo: MediaFileInfo)fun start()fun seekTo(duration: Long, start: Boolean)fun resume()fun pause()fun stop()fun destroy()fun isPlaying(): Booleanfun getCurrentTime(): Longfun getTotalTime(): Long
}

更多看源码吧:
Github源码地址: https://github.com/intbird/VideoPlayerLib
文章来自:http://blog.csdn.net/intbird 转载请说明出处

4.触摸完整代码

有空了可以把这里的UI抽出去,方便后面改动

其实点按监听 和 滑动监听 可以放在一个GestureDetector中
但是我想如果后面按touch挪会方便点,而且难免后面会有其他手势检测
一个类也不可能要承载那么多不同逻辑代码,放不放问题都不大

class TouchController(private val player: IPlayer?, private val iLockCall: ILockCallback?,private val videoTouchCallback: IVideoTouchCallback,private var viewImpl: View) : ILockExecute, ILandscapeExecute {/*** 点击手势解析, 用来点击控制 播放/暂停*/private var tapInterceptor = GestureDetector(videoTouchCallback.getContext(), PlayerTapInterceptor())/*** 触摸手势解析, 用来判断 滑动在屏幕左侧/右侧的纵向滑动, 还是在屏幕中间横向滑动*/private var touchInterceptor = PlayerTouchInterceptor()private val mediaTotalTimeget() = player?.getTotalTime()?: 0Lprivate val mediaCurrentTimeget() = player?.getCurrentTime()?: 0Linit {executeLock(false)}override fun executeLock(lock: Boolean) {if (lock) {viewImpl.setOnTouchListener { _, _ -> iLockCall?.needUnLock(); false }} else {viewImpl.setOnTouchListener { view, event -> touchInterceptor.onTouch(view, event) || tapInterceptor.onTouchEvent(event) }}}override fun onLandscape() {touchInterceptor.viewSizeChange()}override fun onPortrait() {touchInterceptor.viewSizeChange()}inner class PlayerTapInterceptor : SimpleOnGestureListener() {override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {videoTouchCallback?.onSingleTap()return true}override fun onDoubleTap(e: MotionEvent?): Boolean {videoTouchCallback?.onDoubleTap()return true}}data class PlayerMoveBound(val lowBound: Int, var upBound: Int)inner class PlayerTouchInterceptor() : View.OnTouchListener {// 触摸记录private var lastTouchEventX: Float = 0fprivate var lastTouchEventY: Float = 0fprivate var lastTouchType: PlayerTouchType = PlayerTouchType.NONE// 进度视差因子, 优化调节效果private val parallaxX = 1f// 音量视差因子, 优化调节效果private val parallaxYVolume = 4.4f// 亮度视差因子, 优化调节效果private val parallaxYLight = 4.4f// 回调进度阈值, 防止无效的重复调用private val ratioThreshold = 0.01f// 横向滑动控制范围private var allowXAlixRange: Rect? = nullprivate var allowXAlixMoveBound: PlayerMoveBound? = PlayerMoveBound(20, 20)// 纵向滑动控制范围private var allowYAlixRangeLeft: Rect? = nullprivate var allowYAlixRangeRight: Rect? = nullprivate var allowYAlixMoveBound: PlayerMoveBound? = PlayerMoveBound(20, 20)// 进度缓存private var lastProgressInfo = ProgressInfo()// 音量缓存private var adjustVolumeInfo = AdjustInfo()// 亮度缓存private var adjustBrightnessInfo = AdjustInfo()fun viewSizeChange() {allowXAlixRange = nullallowYAlixRangeLeft = nullallowYAlixRangeRight = null}override fun onTouch(v: View?, event: MotionEvent?): Boolean {val viewWidth = v?.width ?: 0val viewHeight = v?.height ?: 0// 不应用滑动if (viewWidth == 0 || viewHeight == 0) {return false}when (event?.actionMasked) {MotionEvent.ACTION_DOWN -> {lastTouchEventX = event.xlastTouchEventY = event.yhandleTouchDown(viewWidth, viewHeight)}MotionEvent.ACTION_MOVE -> {val distanceX = event.x - lastTouchEventXval distanceY = event.y - lastTouchEventYreturn handlerTouchMove(distanceX, distanceY, viewWidth, viewHeight, event)}MotionEvent.ACTION_UP -> {releaseTouchHandler()}else -> {}}return false}private fun handlerTouchMove(distanceX: Float, distanceY: Float, viewWidth: Int, viewHeight: Int, event: MotionEvent): Boolean {return when (lastTouchType) {PlayerTouchType.NONE -> {if (isTouchProgress(distanceX, distanceY, viewWidth, event)) {lastTouchType = PlayerTouchType.TOUCH_PROGRESSvideoTouchCallback.onBeforeDropSeek()}if (isTouchVolume(distanceX, distanceY, viewHeight, event)) {lastTouchType = PlayerTouchType.TOUCH_VOLUME}if (isTouchLight(distanceX, distanceY, viewHeight, event)) {lastTouchType = PlayerTouchType.TOUCH_LIGHT}return lastTouchType != PlayerTouchType.NONE}PlayerTouchType.TOUCH_PROGRESS -> {touchProgress(distanceX, distanceY, viewWidth, event)}PlayerTouchType.TOUCH_VOLUME -> {touchVolume(distanceX, distanceY, viewHeight, event)}PlayerTouchType.TOUCH_LIGHT -> {touchLight(distanceX, distanceY, viewHeight, event)}}}private fun handleTouchDown(viewWidth: Int, viewHeight: Int) {// 横向进度触摸范围if (null == allowXAlixRange) {allowXAlixRange = Rect(0, 0, viewWidth, viewHeight)}if (null == allowYAlixRangeLeft) {allowYAlixRangeLeft = Rect(0, viewHeight / 6 * 1, viewWidth / 2, viewHeight / 6 * 5)}if (null == allowYAlixRangeRight) {allowYAlixRangeRight = Rect(viewWidth / 2, viewHeight / 6 * 1, viewWidth, viewHeight / 6 * 5)}lastProgressInfo.available = falseadjustVolumeInfo.available = falseadjustBrightnessInfo.available = false}private fun isTouchProgress(distanceX: Float, distanceY: Float, viewWidth: Int, event: MotionEvent): Boolean {return allowXAlixRange!!.contains(event.x.toInt(), event.y.toInt())&& (abs(distanceY) < allowXAlixMoveBound!!.lowBound) && (abs(distanceX) > allowXAlixMoveBound!!.upBound)}private fun isTouchVolume(distanceX: Float, distanceY: Float, viewHeight: Int, event: MotionEvent): Boolean {return allowYAlixRangeRight!!.contains(event.x.toInt(), event.y.toInt())&& (abs(distanceX) < allowYAlixMoveBound!!.lowBound) && (abs(distanceY) > allowYAlixMoveBound!!.upBound)}private fun isTouchLight(distanceX: Float, distanceY: Float, viewHeight: Int, event: MotionEvent): Boolean {return allowYAlixRangeLeft!!.contains(event.x.toInt(), event.y.toInt())&& (abs(distanceX) < allowYAlixMoveBound!!.lowBound) && (abs(distanceY) > allowYAlixMoveBound!!.upBound)}private fun releaseTouchHandler() {when (lastTouchType) {PlayerTouchType.NONE -> {}PlayerTouchType.TOUCH_PROGRESS -> {releaseProgressTouch()}PlayerTouchType.TOUCH_VOLUME -> {releaseVolumeTouch()}PlayerTouchType.TOUCH_LIGHT -> {releaseLightTouch()}}lastTouchType = PlayerTouchType.NONE}private fun touchProgress(distanceX: Float, distanceY: Float, viewWidth: Int, event: MotionEvent): Boolean {val radioX = distanceX / viewWidth   // 滑动长度占比// 阈值if (abs(radioX) > 0.01) {// 计算进度值if (!lastProgressInfo.available) {lastProgressInfo = ProgressInfo(0L, mediaTotalTime, mediaCurrentTime)}lastProgressInfo.addIncrease(radioX * parallaxX)videoTouchCallback.onDroppingSeek(lastProgressInfo.progress)// 播放控制// videoTouchCallback?.notifyVideoProgressImpl(newVideoProgressTime, mediaTotalTime)visibleProgressIndicator(true)viewImpl.tvTouchCurrentProgress.text = MediaTimeUtil.formatTime(lastProgressInfo.progress)viewImpl.tvTouchTotalProgress.text = MediaTimeUtil.formatTime(mediaTotalTime)viewImpl.pbTouchProgress.progress = lastProgressInfo.progressUIviewImpl.pbTouchProgress.max = lastProgressInfo.maxValueUI}return true}private fun releaseProgressTouch() {visibleProgressIndicator(false)videoTouchCallback.onAfterDropSeek()}private fun touchVolume(distanceX: Float, distanceY: Float, viewHeight: Int, event: MotionEvent): Boolean {val ratioY = -distanceY / viewHeight   // 滑动高度占比//阈值if (abs(ratioY) > ratioThreshold) {if (!adjustVolumeInfo.available) {adjustVolumeInfo = videoTouchCallback.getVolumeInfo()}adjustVolumeInfo.addIncrease(ratioY * parallaxYVolume)// 音量调节实现让外部去做videoTouchCallback.changeSystemVolumeImpl(adjustVolumeInfo.progress)visibleAdjustIndicator(true)// 调整UIif (adjustVolumeInfo.progress <= 0) viewImpl.adjustIcon.setImageResource(R.drawable.icon_video_player_audio_off)else viewImpl.adjustIcon.setImageResource(R.drawable.icon_video_player_audio_on)viewImpl.adjustProgressBar.progress = adjustVolumeInfo.progressUIviewImpl.adjustProgressBar.max = adjustVolumeInfo.maxValueUI}return true}private fun releaseVolumeTouch() {visibleAdjustIndicator(false)}private fun touchLight(distanceX: Float, distanceY: Float, viewHeight: Int, event: MotionEvent): Boolean {val ratioY = -distanceY / viewHeight   // 滑动高度占比//阈值if (abs(ratioY) > ratioThreshold) {if (!adjustBrightnessInfo.available) {adjustBrightnessInfo = videoTouchCallback.getBrightnessInfo()}adjustBrightnessInfo.addIncrease(ratioY * parallaxYLight)// 亮度调节实现让外部去做videoTouchCallback.changeBrightnessImpl(adjustBrightnessInfo.progress)visibleAdjustIndicator(true)// 调整UIif (adjustBrightnessInfo.progress <= 0) viewImpl.adjustIcon.setImageResource(R.drawable.icon_video_player_light_off)else viewImpl.adjustIcon.setImageResource(R.drawable.icon_video_player_light_on)viewImpl.adjustProgressBar.progress = adjustBrightnessInfo.progressUIviewImpl.adjustProgressBar.max = adjustBrightnessInfo.maxValueUI}return true}private fun releaseLightTouch() {visibleAdjustIndicator(false)}private fun visibleProgressIndicator(visible: Boolean) {if (visible) {if (viewImpl.llTimeIndicatorWrapper.visibility == View.INVISIBLE) {viewImpl.llTimeIndicatorWrapper.visibility = View.VISIBLE}} else {if (viewImpl.llTimeIndicatorWrapper.visibility == View.VISIBLE) {viewImpl.llTimeIndicatorWrapper.visibility = View.INVISIBLE}}}private fun visibleAdjustIndicator(visible: Boolean) {if (visible) {if (viewImpl.llAdjustIndicatorWrapper.visibility == View.INVISIBLE) {viewImpl.llAdjustIndicatorWrapper.visibility = View.VISIBLE}} else {if (viewImpl.llAdjustIndicatorWrapper.visibility == View.VISIBLE) {viewImpl.llAdjustIndicatorWrapper.visibility = View.INVISIBLE}}}}fun destroy() {}
}
enum class PlayerTouchType {NONE, TOUCH_PROGRESS, TOUCH_LIGHT, TOUCH_VOLUME
}
5.控制面板消失时加一些动画过渡,效果稍微好些
private fun toggleVisibleAnimation(visible: Boolean,targetViews: Array<View>,animation: Boolean = true) {if (animation) {for (view in targetViews) {view.animate().alpha(if (visible) 1f else 0f).setDuration(if (visible) visibleDuration else inVisibleDuration).withEndAction {view.visibility = if (visible) View.VISIBLE else View.INVISIBLE}}} else {for (view in targetViews) {view.visibility = if (visible) View.VISIBLE else View.INVISIBLE}}}

4. 资源文件

1. seekbar样式自定义:

1.一定要注意这个 clip


2. seekbar样式兼容
1.低版本的兼容(6.0以下)gravity不生效

  androidx.appcompat.widget.AppCompatSeekBar

2.高版本快捷修改bar颜色api:

   <style><item name="android:colorControlActivated">#1a237e</item><item name="android:colorControlNormal">#00b0ff</item></style>
2. 水波纹效果的版本兼容

3. 格式问题

MediaPlayer部分格式不支持,比如 av01
Exoplayer 文档: https://exoplayer.dev/hello-world.html

4. 沉浸式状态栏
  override fun onWindowFocusChanged(hasFocus: Boolean) {super.onWindowFocusChanged(hasFocus)if (hasFocus) hideSystemUI()}private fun hideSystemUI() {window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKYor View.SYSTEM_UI_FLAG_LAYOUT_STABLEor View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATIONor View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN// Hide the nav bar and status baror View.SYSTEM_UI_FLAG_HIDE_NAVIGATIONor View.SYSTEM_UI_FLAG_FULLSCREEN)}private fun showSystemUI() {window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLEor View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATIONor View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)}
 <style name="lib_media_playerNoTitleBar" parent="Theme.AppCompat.Light.NoActionBar"><item name="android:windowNoTitle">true</item><item name="android:windowActionBar">false</item><item name="android:windowFullscreen">true</item><item name="android:windowContentOverlay">@null</item></style>

后面想到什么再补充以下.

End.
Github源码地址: https://github.com/intbird/VideoPlayerLib
文章来自:http://blog.csdn.net/intbird 转载请说明出处

android media player实现一个可手势滑动控制 + 可以调节分辨率|字幕|倍速的视频播放器(MediaPlayer + ExoPlayer实现)相关推荐

  1. 酷播云html5倍速功能视频播放器,Chrome扩展推荐:一个能16倍速播放的免费视频倍速播放器...

    Video Speed Controller 长时间观看看网课或者影视剧时,啰嗦的内容很容易令人疲劳. 因此许多人在观看视频时,常常习惯将播放速度提升至1.3~1.5倍来补偿视觉接收的差异. 虽然某些 ...

  2. android 播放器 wav 无法播放,对于Android媒体播放器mp3与wav(For android media player mp3 vs. wav)...

    对于Android媒体播放器mp3与wav(For android media player mp3 vs. wav) 我想知道在Android媒体播放器上加载和播放小wav是否比较快的小文件更快. ...

  3. 带有SeekBar的Android Media Player歌曲

    In this tutorial, we'll use the MediaPlayer class to implement a basic Audio Player in our Android A ...

  4. android vr播放器 开发,Android应用开发之Android VR Player(全景视频播放器)- ExoPlayer播放器MPEG-DASH视频播放...

    本文将带你了解Android应用开发之Android VR Player(全景视频播放器)- ExoPlayer播放器MPEG-DASH视频播放,希望本文对大家学Android有所帮助. Androi ...

  5. Android视频播放器Google Exoplayer

    最近开发的项目涉及音频.视频播放,搜索了解到ExoPlayer2.x可以很好的满足需求,现在跟大家分享一下. ExoPlayer库的核心是ExoPlayer接口.ExoPlayer 接口暴露了传统的 ...

  6. android music player实现一个可随机/顺序播放的可加载专辑图片的音频播放器(MediaPlayer)

    音乐播放器 1. 媒体交互框架 1. 服务端 2. 客户端 3. 两端消息交互 2. 播放模式 1. 单曲循环和整体循环 2. 随机播放和顺序播放 3. 几个问题: 1. 合并播放模式 2. 播放指定 ...

  7. android media player setlooping,Android Mediaplayer-一次播放铃声

    我发现了使用事件的简单解决方案-确认答案,因为它可以解决您的问题:) mediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompl ...

  8. android体感游戏主机,体感手势引擎+智能电视机顶盒+3D深度摄像头,速盟打造基于Android的体感游戏平台#36氪开放日#...

    相信36氪的读者对Kinect 一定有所了解.这是由微软开发,应用于 Xbox 360 主机的一种3D体感摄影机,利用即时动态捕捉.影像辨识.麦克风输入.语音辨识等功能带给玩家"免控制器的游 ...

  9. android能播放4k视频格式,安卓APP,无广告支持多种格式的万能视频播放器

    原标题:安卓APP,无广告支持多种格式的万能视频播放器 万能视频播放器 万能视频播放器是一款专业的视频播放工具.它支持所有视频格式,支持 4K/超高清视频文件,并且能够高清播放.它是安卓手机和平板上欣 ...

最新文章

  1. clone database and rename
  2. JSONP跨域请求数据报错 “Unexpected token :”的解决办法
  3. bzoj2194 快速傅立叶之二
  4. poj3342Party at Hali-Bula(树形dp)
  5. OpenStack vlan教程 (操作篇)
  6. linux网络服务偶尔失效,判断linux下的网络服务是否正常启动
  7. python模块:运行机制与编写方法
  8. linux 运行jar main,Maven打包生成jar包并在linux下启动main方法
  9. 这届 360 公关不行
  10. 如何下载MySQL的驱动包
  11. 企业私有云搭建与作用
  12. 教学向|如何快速入门maya制作动画,萌新也能冲
  13. 机器学习经典模型简单使用及归一化(标准化)影响
  14. [BIM]BIM中IFD介绍
  15. 计算机网络英语作文150字,微信投票的英语,写一篇关于网络投票看法的英语作文150字左右...
  16. filebeat k8s健康探针
  17. OpenCV小案例(2)——判断一张图片中多少种颜色
  18. 用Python助女神发朋友圈
  19. 考研数学:罗尔定理的推论
  20. DevOps ACA 阿里云效持续交付流水线(十)

热门文章

  1. ArcGIS的符号选择器(Symbol Selector)为空的解决办法
  2. “消费盲返”为什么可以在短短几天迅速爆火?
  3. 企业服务器固态硬盘寿命,SSD固态硬盘使用寿命短?_企业存储技术与评测-中关村在线...
  4. php is numeric用法,PHP使用 is_numeric的实例解析
  5. 自信心受挫,该如何让项目团队成员重新振作起来
  6. 免疫系统与冠状病毒之争:抗体水平下降时,T细胞会支持你
  7. FlashDB移植与应用
  8. 爱奇艺体育获5亿元战略融资 ,IDG资本、汇盈博润领投
  9. 如何在DW中运行PHP文件
  10. hashcat破解WiFi显示No hashes loaded的解决方法