介绍
这是一篇关于多视频缓存的技术方案
诞生背景
希望能将视频缓存下来,然后在需要的时候立马播放。
需要的配件
1、AndroidVideoCache
2、项目上通用的播放器
源码
功能比较简单:直接上源码
...
import android.util.Log
import com.danikula.videocache.HttpProxyCacheServer
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
/**
* 多视频缓存帮助类
* */
object MultiplayerHelper {
private val TAG = MultiplayerHelper::class.java.simpleName
var appScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
var job: Job? = null
val handler = CoroutineExceptionHandler { _, exception ->
Log.d(TAG, "CoroutineExceptionHandler got $exception")
}
//缓存代理
private val httpProxyCacheServer: HttpProxyCacheServer by lazy {
App.get().appViewModelProvider.get(AppViewModel::class.java).httpProxyCacheServer
}
private val testUrlList = arrayListOf<String>(
"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
"http://vjs.zencdn.net/v/oceans.mp4",
"https://media.w3.org/2010/05/sintel/trailer.mp4",
"http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4"
)
fun test() {
cacheLinkListVideos(testUrlList)
}
private var videoLinkListSet = mutableSetOf<String>()
private val videoPlayerList = mutableListOf<VideoPlayerEntity>()
/**
* 分批缓存视频
* @param groupSize 每次可缓存多少视频
* @param delayTime 缓存视频间隔
* */
@JvmStatic
fun cacheLinkListVideos(
videoString: List<String>?,
groupSize: Int = 3,
delayTime: Long = 500L
) {
if (videoString == null) {
Log.d(TAG, "cacheLinkListVideos: videoString == null")
return
}
if (job != null && job?.isActive == true) {
Log.d(TAG, "cacheLinkListVideos: cancel")
job?.cancel()
videoPlayerList.forEach {
Log.d(
TAG, "cacheLinkListVideos: cancel 释放播放器 ${it.num}"
)
it.videoPlayer.release()
}
videoPlayerList.clear()
}
Log.d(TAG, "cacheLinkListVideos: start")
videoLinkListSet.addAll(videoString)
val splitList = splitList(videoLinkListSet.mapIndexed { i, it ->
it to "视频序号 $i"
}.filter {
!httpProxyCacheServer.isCached(it.first)
}, groupSize)
Log.d(TAG, "cacheLinkListVideos: 拆解后的 $splitList")
job = appScope.launch(handler) {
for (i in 0 until groupSize) {
val videoPlayer = VideoPlayer()
videoPlayerList.add(VideoPlayerEntity(videoPlayer, i, true))
}
Log.d(TAG, "cacheLinkListVideos: 创建播放器数量${videoPlayerList.size}")
splitList.forEachIndexed() { i, res ->
/**
* 内部工作函数
* */
suspend fun doWork(videoString: Pair<String, String>) {
val currentVideoPlayer = videoPlayerList.firstOrNull { it.isPlay }
if (currentVideoPlayer == null) {
Log.d(
TAG,
"当前没有播放器释放出来"
)
doWork(videoString)
return
}
Log.d(
TAG,
"doWork: currentVideoPlayer num = ${currentVideoPlayer.num}}"
)
currentVideoPlayer.isPlay = false
delay(500)
withContext(Dispatchers.Main) {
currentVideoPlayer.videoPlayer.startPlayVideo(
null,
httpProxyCacheServer.getProxyUrl(videoString.first)
)
}
runCatching {
httpProxyCacheServer.isCached(videoString.first)
}.onSuccess {
if (it) {
Log.d(
TAG,
"doWork: 播放器释放 ${
httpProxyCacheServer.getProxyUrl(videoString.first)
}"
)
currentVideoPlayer.isPlay = true
} else {
Log.d(
TAG,
"doWork:当前没有成功缓存,继续等待 ${videoString.second} 播放器序号${currentVideoPlayer.num} ${Thread.currentThread().name} "
)
val jobRepeat = async {
repeat(Int.MAX_VALUE) {
delay(500)
Log.d(
TAG,
"doWork: repeat$it 播放器序号${currentVideoPlayer.num}"
)
if (httpProxyCacheServer.isCached(videoString.first)) {
Log.d(
TAG,
"doWork: 播放器释放 repeat 播放器${currentVideoPlayer.num}${
httpProxyCacheServer.getProxyUrl(videoString.first)
}"
)
currentVideoPlayer.isPlay = true
return@async true
}
}
}
if (jobRepeat.await() == true) {
Log.d(TAG, "doWork: 计时结束")
jobRepeat.cancel()
}
}
}.onFailure {
Log.d(TAG, "doWork:error= ${it.message}")
job?.cancel()
}
}
res.map { ress ->
async {
doWork(ress)
}
}.awaitAll()
Log.d(
TAG,
"执行下次记录 当前$i"
)
delay(delayTime)
}
yield()
Log.d(
TAG, "功能结束 开始释放播放器"
)
withContext(Dispatchers.Main) {
videoPlayerList.forEach {
Log.d(
TAG, "释放播放器 ${it.num}"
)
it.videoPlayer.release()
}
}
}
}
/**
* 拆分集合
* @param messagesList 拆解前的集合
* @param groupSize 拆解因子
* */
private fun <T> splitList(
messagesList: List<T>,
groupSize: Int
): List<List<T>> {
val length = messagesList.size
// 计算可以分成多少组
val num = (length + groupSize - 1) / groupSize
val newList: MutableList<List<T>> = ArrayList(num)
for (i in 0 until num) {
// 开始位置
val fromIndex = i * groupSize
// 结束位置
val toIndex = if ((i + 1) * groupSize < length) (i + 1) * groupSize else length
newList.add(messagesList.subList(fromIndex, toIndex))
}
return newList
}
/**
* 多播放器实体类
* */
data class VideoPlayerEntity(val videoPlayer: VideoPlayer, val num: Int, var isPlay: Boolean)
}