demo: https://download.csdn.net/download/BirdEatBug/19033060 无需积分
1、播放器:IjkPlayer、2、点赞库 :heartLirary、3、EventBus(用于视频资源回收调用)、4、弹窗:-》'com.kongzue.dialog_v3x:dialog:3.2.4' (视频加载等待)、5、下拉上拉库SmallRefresh 6、so库 使用GsyPlayer so 库 api 'com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-ex_so:v8.1.3-jitpack'
目录结构
heartlibraary (子依赖)-->vpplayer、
kgplayer (子依赖)-->vpplayer、
1、activity_list_video.xml activity主界面
2、主Activity
package com.qiqilego.vpplayer
import android.os.Handler
import android.view.View
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.qiqilego.vpplayer.adapter.VideoListPagerAdapter
import com.qiqilego.vpplayer.event.VideoIndexEvent
import com.qiqilego.vpplayer.fragment.VideoPlayForActivityFragment
import com.scwang.smart.refresh.footer.ClassicsFooter
import com.scwang.smart.refresh.header.MaterialHeader
import kotlinx.android.synthetic.main.activity_list_video.*
class VideoListPlayerActivity : BaseActivity() {
companion object {
const val TEST_URL = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
}
override fun getLayoutId() = R.layout.activity_list_video
override fun setTitle() = ""
private val handler = Handler()
private lateinit var adapter: VideoListPagerAdapter
override fun initView() {
//初始化ViewPager
adapter = VideoListPagerAdapter(this)
viewPager.adapter = adapter
viewPager.orientation = ViewPager2.ORIENTATION_VERTICAL
//去除左右阴影
if (viewPager.getChildAt(0) is RecyclerView) {
viewPager.getChildAt(0).overScrollMode = View.OVER_SCROLL_NEVER
}
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
//发送EventBus
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
eventBus.post(VideoIndexEvent(position))
}, 500)
}
})
refreshView.setRefreshHeader(MaterialHeader(this).apply {
//设置强调颜色
setProgressBackgroundColorSchemeColor(resources.getColor(R.color.white))
})
refreshView.setRefreshFooter(ClassicsFooter(this).apply {
//设置强调颜色
setAccentColorId(R.color.white)
//设置主题颜色
setPrimaryColorId(R.color._000000)
//设置刷新完成显示的停留时间
setFinishDuration(0)
})
refreshView.setOnRefreshListener {
val list = mutableListOf()
for (index in 0..20) {
list.add(VideoPlayForActivityFragment(TEST_URL, index))
}
adapter.addFirstData(list as MutableList)
refreshView.finishRefresh(500)
}
refreshView.setOnLoadMoreListener {
val list = mutableListOf()
for (index in 0..10) {
list.add(VideoPlayForActivityFragment(TEST_URL, index))
}
adapter.addData(list as MutableList)
refreshView.finishLoadMore(500)
}
}
override fun initDate() {
refreshView.autoRefresh()
}
}
3、ViewPager2适配器
package com.qiqilego.vpplayer.adapter
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.qiqilego.vpplayer.VideoListPlayerActivity
import com.qiqilego.vpplayer.fragment.VideoPlayForActivityFragment
import kotlinx.android.synthetic.main.activity_list_video.*
class VideoListPagerAdapter(private val activity: AppCompatActivity) : FragmentStateAdapter(activity) {
private val dataList = mutableListOf()
fun addData(newData: MutableList) {
if (newData.isNotEmpty()) {
val oldSize = dataList.size
//安全验证 》=50条 删除之前缓存数据
if (oldSize + newData.size >= 50) {
newData.add(
0,
VideoPlayForActivityFragment(
(dataList.last() as VideoPlayForActivityFragment).videoUrl,
-1
)
)
dataList.clear()
dataList.addAll(newData)
//重新赋值下标
dataList.forEachIndexed { index, it ->
if (it is VideoPlayForActivityFragment) {
it.position = index
}
}
notifyDataSetChanged()
if (activity is VideoListPlayerActivity) {
activity.viewPager.setCurrentItem(0, false)
}
} else {
dataList.addAll(newData)
dataList.forEachIndexed { index, it ->
if (it is VideoPlayForActivityFragment) {
it.position = index
}
}
notifyItemRangeInserted(oldSize, dataList.size)
}
}
}
fun addFirstData(newData: MutableList) {
if (newData.isNotEmpty()) {
dataList.clear()
dataList.addAll(newData)
dataList.forEachIndexed { index, it ->
if (it is VideoPlayForActivityFragment) {
it.position = index
}
}
notifyDataSetChanged()
}
}
fun getData() = dataList
override fun getItemCount() = dataList.size
override fun createFragment(position: Int): Fragment {
return dataList[position]
}
override fun getItemId(position: Int): Long {
return dataList[position].hashCode().toLong()
}
}
1、视频详情xml
2、播放失败xml
3、视频播放Fragment
package com.qiqilego.vpplayer.fragment
import android.annotation.SuppressLint
import android.os.Handler
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.appcompat.app.AppCompatActivity
import com.kongzue.dialog.v3.WaitDialog
import com.kuaige.player.listener.VideoListener
import com.lzj.douyin.redheart.library.RedHeartLayout
import com.qiqilego.vpplayer.R
import com.qiqilego.vpplayer.event.VideoIndexEvent
import kotlinx.android.synthetic.main.fragment_video_recommend.*
import kotlinx.android.synthetic.main.video_play_fail.*
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import tv.danmaku.ijk.media.player.IMediaPlayer
import kotlin.math.abs
/**
* 适用于Activity管理
*/
class VideoPlayForActivityFragment(
var videoUrl: String?,
var position: Int
) : BaseFragment(),
VideoListener, View.OnClickListener,
RedHeartLayout.HeartTouchListener {
private var isPrepared = false
//是否释放过视频资源
private var isRelease = false
//是否执行过stop
private var selfStop = false
override fun getLayoutId() = R.layout.fragment_video_recommend
override fun setTitle() = ""
private val handler = Handler()
@SuppressLint("SetTextI18n")
override fun viewCreated(view: View) {
video?.run {
setVideoListener(this@VideoPlayForActivityFragment)
//硬件加速Gpu渲染
setEnableMediaCodec(true)
}
}
@SuppressLint("ClickableViewAccessibility")
override fun initDate() {
tvPlayRetry.setOnClickListener(this)
ivPlayer.setOnClickListener(this)
//点赞按钮
ivLike.setOnClickListener(this)
ivLike.tag = false
ivLike.setImageResource(R.drawable.ic_gray_heart)
ivHeart.setHeartTouchListener(this)
}
override fun onResume() {
super.onResume()
if (!video.isPlaying && isPrepared) {
if (selfStop || isRelease) {
selfStop = false
video.reset()
video.setPath(videoUrl)
video.load()
} else {
video.start()
ivVideoThumb.visibility = GONE
}
ivPlayer.visibility = GONE
}//首次拉取视频uri进行播放
else if (!video.isPlaying && !isPrepared) {
video.run {
setPath(videoUrl)
load()
}
ivPlayer.visibility = GONE
}
}
/**
* Eventbus机制 用来释放video资源引用 当前播放视频的前后5页不被释放、其余都要释放视频缓存
* @param event.position 为当前播放页视频下标
*/
@Subscribe(threadMode = ThreadMode.MAIN)
override fun onMessageEvent(event: Any?) {
super.onMessageEvent(event)
if (event is VideoIndexEvent) {
val needRelease = abs(position - event.position) > 4
if (needRelease && !isRelease) {
video?.post {
video?.stop()
video?.release()
videoUrl = null
isRelease = true
}
}
}
}
override fun onPause() {
super.onPause()
ivPlayer.visibility = VISIBLE
video.pause()
}
override fun onStop() {
super.onStop()
video?.stop()
selfStop = true
ivPlayer.visibility = VISIBLE
}
override fun onDestroy() {
super.onDestroy()
video?.stop()
video?.release()
}
override fun onSeekComplete(player: IMediaPlayer?) {
}
override fun onInfo(player: IMediaPlayer?, p1: Int, p2: Int): Boolean {
return false
}
override fun onVideoSizeChanged(player: IMediaPlayer?, p1: Int, p2: Int, p3: Int, p4: Int) {
}
override fun onBufferingUpdate(player: IMediaPlayer?, p1: Int) {
}
override fun onPrepared(player: IMediaPlayer?) {
WaitDialog.dismiss()
isPrepared = true
isRelease = false
if (player != null && !player.isPlaying && isResumed) {
player.start()
ivVideoThumb.visibility = GONE
}
videoPlayFail?.visibility = GONE
}
override fun onCompletion(player: IMediaPlayer?) {
//循环播放
if (isResumed) {
player?.start()
}
}
override fun onError(player: IMediaPlayer?, p1: Int, p2: Int): Boolean {
WaitDialog.dismiss()
videoPlayFail?.visibility = VISIBLE
return false
}
override fun onClick(v: View?) {
when (v) {
tvPlayRetry -> {
WaitDialog.show(activity as AppCompatActivity, R.string.video_loading)
videoPlayFail.visibility = GONE
video.reset()
video.setPath(videoUrl)
video.load()
}
ivPlayer -> {
if (selfStop) {
selfStop = false
video.reset()
video.setPath(videoUrl)
video.load()
} else {
video.start()
ivVideoThumb.visibility = GONE
}
ivPlayer.visibility = GONE
}
ivLike -> {
if (ivLike.tag == false) {
ivLike.tag = true
ivLike.setImageResource(R.drawable.ic_red_heart)
} else {
ivLike.tag = false
ivLike.setImageResource(R.drawable.ic_gray_heart)
}
}
}
}
override fun onDoubleBefore() {
handler.postDelayed({
if (video.isPlaying) {
video.pause()
ivPlayer.visibility = VISIBLE
}
}, 200)
}
override fun onDoubleListener() {
handler.removeCallbacksAndMessages(null)
if (ivLike.tag == false) {
ivLike.tag = true
ivLike.setImageResource(R.drawable.ic_red_heart)
} else {
ivLike.tag = false
ivLike.setImageResource(R.drawable.ic_gray_heart)
}
}
override fun onDoubleAfter() {
}
}
3、EventBus 实体类
data class VideoIndexEvent(val position: Int)
1、BaseActivity
package com.qiqilego.vpplayer
import android.graphics.Typeface
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
abstract class BaseActivity : AppCompatActivity() {
lateinit var eventBus: EventBus
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.hide()
eventBus = EventBus.getDefault()
if (needInputSoftMode())
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
setViewBefore()
if (getLayoutId() != null) {
setContentView(getLayoutId()!!)
} else {
setContentView(getLayoutView())
}
initView()
initDate()
}
/**
* 布局id
*/
abstract fun getLayoutId(): Int?
open fun getLayoutView(): View? = null
/**
* 状态栏颜色
*/
open fun statusBarColor() = -1
/**
* 设置标题
*/
abstract fun setTitle(): String
//设置标题背景 资源id
open fun setTitleBackGround(): Int? = null
//设置ttf格式字体
open fun setTitleFontStyle(): Typeface? = null
/**
* 初始化
*/
abstract fun initView()
/**
* 数据初始化
*/
abstract fun initDate()
/**
* 左侧返回按钮
*/
open fun needBackIcon() = false
/**
* 是否需要返回监听事件
*/
open fun needBackEvent() = true
/**
* 右侧返回按钮
*/
open fun needRightIcon() = false
/**
* 右侧文字按钮
*/
open fun needRightTv() = false
/**
* 输入法高度自适应EdiTText
*/
open fun needInputSoftMode() = false
/**
* 隐私游乐园
*/
open fun needTeamTimeTitle() = false
/**
* setContentView 之前执行的逻辑
*/
open fun setViewBefore() {}
override fun onStart() {
super.onStart()
if (!EventBus.getDefault().isRegistered(this))
EventBus.getDefault().register(this)
}
override fun onDestroy() {
super.onDestroy()
EventBus.getDefault().unregister(this)
}
@Subscribe(threadMode = ThreadMode.MAIN)
open fun onMessageEvent(event: Any?) {
}
}
2、BaseFragment
package com.qiqilego.vpplayer.fragment
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
abstract class BaseFragment : Fragment() {
lateinit var eventBus: EventBus
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(getLayoutId(), container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewCreated(view)
eventBus=EventBus.getDefault()
initDate()
}
/**
* 布局id
*/
abstract fun getLayoutId(): Int
/**
* 设置标题
*/
abstract fun setTitle(): String
/**
* view初始化
*/
abstract fun viewCreated(view: View)
/**
* view监听绑定
*/
abstract fun initDate()
/**
* 左侧返回按钮
*/
open fun needBackIcon() = false
/**
* 右侧返回按钮
*/
open fun needRightIcon() = false
override fun onAttach(context: Context) {
super.onAttach(context)
if (!EventBus.getDefault().isRegistered(this))
EventBus.getDefault().register(this)
}
override fun onDetach() {
super.onDetach()
EventBus.getDefault().unregister(this)
}
@Subscribe(threadMode = ThreadMode.MAIN)
open fun onMessageEvent(event: Any?) {
}
}
1、图片资源
drawable 资源
gradient_gray_down.xml
white_corner_25.xml
drawable-xhdpi
ic_find_video_play-> ic_gray_heart-> ic_red_heart->
color 及string 文件
要用到的color
相关string.xml
仿抖音
网络不给力,请稍后重试!
点击重试
视频重新加载中…
vpplayer----------->build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.qiqilego.vpplayer"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation project(path: ':kgplayer')
implementation project(path: ':heartlibrary')
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// SmartRefreshLayout 刷新框架
implementation 'com.scwang.smart:refresh-layout-kernel:2.0.3'
//经典刷新头
implementation 'com.scwang.smart:refresh-header-classics:2.0.3'
implementation 'com.scwang.smart:refresh-header-material:2.0.3'
//Event bus
kapt "org.greenrobot:eventbus-annotation-processor:3.2.0"
implementation 'com.kongzue.dialog_v3x:dialog:3.2.4'
}
kapt {
arguments {
arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
}
}
如图
kgplayer ---------------->build.gradle
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
}
/* sourceSets {
main {
jniLibs.srcDirs = ['jniLibs']
}
}*/
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
api "tv.danmaku.ijk.media:ijkplayer-java:$rootProject.ijkPlayerVersion"
api 'com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-ex_so:v8.1.3-jitpack'
api 'org.greenrobot:eventbus:3.2.0'
}
heartlibrary------------->build.gradle
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
根目录build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.4.32"
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
ext {
compileSdkVersion = 30
buildToolsVersion = "30.0.3"
minSdkVersion = 21
targetSdkVersion = 30
appTargetSdkVersion = 30
supportLibraryVersion = '30.0.3'
ijkPlayerVersion = '0.8.8'
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
版本信息:gradle-6.5 android studio 4.1.1 Kotlin版本 "1.4.32"
sdk版本参考 vpplayer.build.gradle