[宣传视频]merge: 合并V5.2.5代码,调整因common目录结构改变带来的修改;
This commit is contained in:
@@ -3,11 +3,10 @@
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".wigets.video.VideoPlayerActivity"
|
||||
android:name=".wigets.media.MediaPlayerActivity"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:process=":video_ad"
|
||||
android:resizeableActivity="false"
|
||||
android:resumeWhilePausing="true"
|
||||
android:screenOrientation="landscape"
|
||||
|
||||
@@ -19,6 +19,12 @@ class OchCommonConst {
|
||||
fun getSweeperUrl(): String {
|
||||
return FunctionBuildConfig.urlJson.sweeperUrl
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEagleMisUrl(): String {
|
||||
return FunctionBuildConfig.urlJson.eagleMisUrl
|
||||
}
|
||||
|
||||
// token 失效 重新获取token
|
||||
const val WAIT_TAKEN = 100046
|
||||
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.mogo.och.common.module.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* @author:liuhai
|
||||
* @date:2017/10/14 14:47
|
||||
* @modifier:ubt
|
||||
* @modify_date:2017/10/14 14:47
|
||||
* 文件缓存存储
|
||||
* version
|
||||
*/
|
||||
|
||||
public class FileUtils {
|
||||
/**
|
||||
* 获取应用专属缓存目录
|
||||
* android 4.4及以上系统不需要申请SD卡读写权限
|
||||
* 因此也不用考虑6.0系统动态申请SD卡读写权限问题,切随应用被卸载后自动清空 不会污染用户存储空间
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param type 文件夹类型 可以为空,为空则返回API得到的一级目录
|
||||
* @return 缓存文件夹 如果没有SD卡或SD卡有问题则返回内存缓存目录,否则优先返回SD卡缓存目录
|
||||
*/
|
||||
public static String getCacheDirectory(Context context, String type) {
|
||||
File appCacheDir = getExternalCacheDirectory(context, type);
|
||||
if (appCacheDir == null) {
|
||||
appCacheDir = getInternalCacheDirectory(context, type);
|
||||
}
|
||||
|
||||
if (appCacheDir == null) {
|
||||
Log.e("FileUtils", "getCacheDirectory fail ,the reason is mobile phone unknown exception !");
|
||||
} else {
|
||||
if (!appCacheDir.exists() && !appCacheDir.mkdirs()) {
|
||||
Log.e("FileUtils", "getCacheDirectory fail ,the reason is make directory fail !");
|
||||
}
|
||||
}
|
||||
Log.d("FileUtils", "appCacheDir===" + appCacheDir.getPath() + File.separator);
|
||||
return appCacheDir.getPath() + File.separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SD卡缓存目录
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param type 文件夹类型 如果为空则返回 /storage/emulated/0/Android/data/app_package_name/cache
|
||||
* 否则返回对应类型的文件夹如Environment.DIRECTORY_PICTURES 对应的文件夹为 .../data/app_package_name/files/Pictures
|
||||
* {@link android.os.Environment#DIRECTORY_MUSIC},
|
||||
* {@link android.os.Environment#DIRECTORY_PODCASTS},
|
||||
* {@link android.os.Environment#DIRECTORY_RINGTONES},
|
||||
* {@link android.os.Environment#DIRECTORY_ALARMS},
|
||||
* {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
|
||||
* {@link android.os.Environment#DIRECTORY_PICTURES}, or
|
||||
* {@link android.os.Environment#DIRECTORY_MOVIES}.or 自定义文件夹名称
|
||||
* @return 缓存目录文件夹 或 null(无SD卡或SD卡挂载失败)
|
||||
*/
|
||||
public static File getExternalCacheDirectory(Context context, String type) {
|
||||
File appCacheDir = null;
|
||||
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
|
||||
if (TextUtils.isEmpty(type)) {
|
||||
appCacheDir = context.getExternalCacheDir();
|
||||
} else {
|
||||
appCacheDir = context.getExternalFilesDir(type);
|
||||
}
|
||||
|
||||
if (appCacheDir == null) {// 有些手机需要通过自定义目录
|
||||
appCacheDir = new File(Environment.getExternalStorageDirectory(), "Android/data/" + context.getPackageName() + "/cache/" + type);
|
||||
}
|
||||
|
||||
if (appCacheDir == null) {
|
||||
Log.e("FileUtils", "getExternalDirectory fail ,the reason is sdCard unknown exception !");
|
||||
} else {
|
||||
if (!appCacheDir.exists() && !appCacheDir.mkdirs()) {
|
||||
Log.e("FileUtils", "getExternalDirectory fail ,the reason is make directory fail !");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e("FileUtils", "getExternalDirectory fail ,the reason is sdCard nonexistence or sdCard mount fail !");
|
||||
}
|
||||
return appCacheDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内存缓存目录
|
||||
*
|
||||
* @param type 子目录,可以为空,为空直接返回一级目录
|
||||
* @return 缓存目录文件夹 或 null(创建目录文件失败)
|
||||
* 注:该方法获取的目录是能供当前应用自己使用,外部应用没有读写权限,如 系统相机应用
|
||||
*/
|
||||
public static File getInternalCacheDirectory(Context context, String type) {
|
||||
File appCacheDir = null;
|
||||
if (TextUtils.isEmpty(type)) {
|
||||
appCacheDir = context.getCacheDir();// /data/data/app_package_name/cache
|
||||
} else {
|
||||
appCacheDir = new File(context.getFilesDir(), type);// /data/data/app_package_name/files/type
|
||||
}
|
||||
|
||||
if (!appCacheDir.exists() && !appCacheDir.mkdirs()) {
|
||||
Log.e("FileUtils", "getInternalDirectory fail ,the reason is make directory fail !");
|
||||
}
|
||||
return appCacheDir;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除缓存文件
|
||||
*
|
||||
* @param context
|
||||
* @param fileName 视频文件名称
|
||||
*/
|
||||
public static void clearCacheFile(Context context, String fileName) {
|
||||
String filepath = getCacheDirectory(context, "");
|
||||
File file = new File(filepath + fileName);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static final int NOT_FOUND = -1;
|
||||
|
||||
/**
|
||||
* The Unix separator character.
|
||||
*/
|
||||
private static final char UNIX_NAME_SEPARATOR = '/';
|
||||
|
||||
/**
|
||||
* The Windows separator character.
|
||||
*/
|
||||
private static final char WINDOWS_NAME_SEPARATOR = '\\';
|
||||
|
||||
/**
|
||||
* The extension separator character.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
public static final char EXTENSION_SEPARATOR = '.';
|
||||
|
||||
public static String getExtension(final String filename) {
|
||||
if (filename == null) {
|
||||
return null;
|
||||
}
|
||||
final int index = indexOfExtension(filename);
|
||||
if (index == NOT_FOUND) {
|
||||
return "";
|
||||
} else {
|
||||
return filename.substring(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static int indexOfExtension(final String filename) {
|
||||
if (filename == null) {
|
||||
return NOT_FOUND;
|
||||
}
|
||||
final int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR);//点的位置
|
||||
final int lastSeparator = indexOfLastSeparator(filename);//最后一个斜杠的位置
|
||||
return lastSeparator > extensionPos ? NOT_FOUND : extensionPos;
|
||||
}
|
||||
|
||||
public static int indexOfLastSeparator(final String filename) {
|
||||
if (filename == null) {
|
||||
return NOT_FOUND;
|
||||
}
|
||||
final int lastUnixPos = filename.lastIndexOf(UNIX_NAME_SEPARATOR);//unix的/
|
||||
final int lastWindowsPos = filename.lastIndexOf(WINDOWS_NAME_SEPARATOR);// windows的\
|
||||
return Math.max(lastUnixPos, lastWindowsPos);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String url = "https://img.zhidaohulian.com/fileServer/online_car_hailing/1676357834634/5.m4v";
|
||||
String[] fileName = url.split("/");
|
||||
String fileSuffix = getExtension(fileName[fileName.length - 1]);
|
||||
System.out.println(fileSuffix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.mogo.och.common.module.wigets.media
|
||||
|
||||
data class MediaDataList(val medias: MutableList<MediaItem>)
|
||||
|
||||
data class MediaItem(
|
||||
var fileUrl: String,
|
||||
var fileType: Int,
|
||||
var coverImageUrl: String,
|
||||
var title: String
|
||||
) {
|
||||
companion object {
|
||||
const val MEDIA_TYPE_IMAGE = 1
|
||||
const val MEDIA_TYPE_VIDEO = 2
|
||||
}
|
||||
|
||||
fun isImageType(): Boolean {
|
||||
return this.fileType == MEDIA_TYPE_IMAGE
|
||||
}
|
||||
|
||||
fun isVideoType(): Boolean {
|
||||
return this.fileType == MEDIA_TYPE_VIDEO
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package com.mogo.och.common.module.wigets.media
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.mogo.commons.AbsMogoApplication
|
||||
import com.mogo.commons.debug.DebugConfig
|
||||
import com.mogo.eagle.core.data.BaseData
|
||||
import com.mogo.eagle.core.data.config.FunctionBuildConfig
|
||||
import com.mogo.eagle.core.function.call.telematic.CallerTelematicManager
|
||||
import com.mogo.eagle.core.network.MoGoRetrofitFactory
|
||||
import com.mogo.eagle.core.utilcode.util.GsonUtils
|
||||
import com.mogo.eagle.core.utilcode.util.NetworkUtils
|
||||
import com.mogo.eagle.core.utilcode.util.UiThreadHandler
|
||||
import com.mogo.och.common.module.biz.constant.OchCommonConst
|
||||
import com.mogo.och.common.module.biz.network.OchCommonServiceCallback
|
||||
import com.mogo.och.common.module.biz.network.OchCommonSubscribeImpl
|
||||
import com.mogo.och.common.module.biz.network.interceptor.transformTry
|
||||
import com.mogo.och.common.module.wigets.media.MediaItem.Companion.MEDIA_TYPE_IMAGE
|
||||
import com.mogo.och.common.module.wigets.media.MediaItem.Companion.MEDIA_TYPE_VIDEO
|
||||
import io.reactivex.Observable
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.Query
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* 广告视频数据源 管理类
|
||||
* 1.第一优先级:从管理后台拿对应车型的宣传视频
|
||||
* 如果无网络或无司机SN或请求异常 失败5次后,立马使用第二优先级本地数据播放
|
||||
* 失败尝试时间间隔:1秒
|
||||
* 2.第二优先级:使用本地数据播放
|
||||
* 3.请求管理后台数据成功后:间隔5分钟再次检查
|
||||
*/
|
||||
object MediaDataSourceManager {
|
||||
private val TAG = MediaDataSourceManager::class.java.simpleName
|
||||
|
||||
private const val RETRY_MAX_COUNT = 5
|
||||
|
||||
private var mRetryCount = 0
|
||||
|
||||
private val mNetworkService: IMediaNetworkApi =
|
||||
MoGoRetrofitFactory.getInstance(OchCommonConst.getEagleMisUrl())
|
||||
.create(IMediaNetworkApi::class.java)
|
||||
|
||||
private var driverSnCache = ""
|
||||
|
||||
private val driverSn: String
|
||||
get() {
|
||||
val serverToken = CallerTelematicManager.getServerToken()
|
||||
if (serverToken != driverSnCache && serverToken.isNotEmpty()) {
|
||||
driverSnCache = serverToken
|
||||
}
|
||||
return driverSnCache
|
||||
}
|
||||
|
||||
val context: Context
|
||||
get() {
|
||||
return AbsMogoApplication.getApp()
|
||||
}
|
||||
|
||||
private val mLastMediaDataSourceList = mutableListOf<MediaItem>()
|
||||
|
||||
private var mHasEverGetMediaDataFromMis = false
|
||||
|
||||
private val mMediaDataSourceListenerMap: ConcurrentHashMap<String, IMediaDataSourceListener> =
|
||||
ConcurrentHashMap()
|
||||
|
||||
private val getAdDataSourceLoopRunnable = Runnable {
|
||||
startGetMediaDataSourceLoop()
|
||||
}
|
||||
|
||||
fun init(tag: String, dataSourceListener: IMediaDataSourceListener) {
|
||||
if (!mMediaDataSourceListenerMap.containsKey(tag)) {
|
||||
mMediaDataSourceListenerMap[tag] = dataSourceListener
|
||||
}
|
||||
val isSassProject = isSassProject()
|
||||
MediaPlayLogger.printInfoLog("init, 初始化环境 isSassProject=$isSassProject")
|
||||
if (isSassProject) {
|
||||
MediaPlayLogger.printInfoLog("init, 开始检查后台配置数据")
|
||||
startGetMediaDataSourceLoop()
|
||||
} else {
|
||||
MediaPlayLogger.printInfoLog("init, 使用本地配置数据初始化")
|
||||
//非Sass环境下后端没有实现可配置,默认只走本地配置
|
||||
val localAdDataList = getMediaDataFromLocalConfig()
|
||||
updateMediaDataSource(localAdDataList)
|
||||
}
|
||||
}
|
||||
|
||||
fun unInit(tag: String) {
|
||||
MediaPlayLogger.printInfoLog("unInit")
|
||||
removeGetMediaDataSourceLoop()
|
||||
if (mMediaDataSourceListenerMap.containsKey(tag)) {
|
||||
mMediaDataSourceListenerMap.remove(tag)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是Sass环境
|
||||
*/
|
||||
private fun isSassProject(): Boolean {
|
||||
return DebugConfig.getProjectFlavor().lowercase().contains("saas")
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun startGetMediaDataSourceLoop() {
|
||||
MediaPlayLogger.printInfoLog("startGetMediaDataSourceLoop, 开始执行后台配置数据检查")
|
||||
removeGetMediaDataSourceLoop()
|
||||
// 失败5次,且从来没有从MIS获取配置信息成功过,先试用本地数据播放
|
||||
if (mRetryCount == RETRY_MAX_COUNT && !mHasEverGetMediaDataFromMis) {
|
||||
val localAdDataList = getMediaDataFromLocalConfig()
|
||||
updateMediaDataSource(localAdDataList)
|
||||
MediaPlayLogger.printErrorLog("startGetMediaDataSourceLoop:失败${mRetryCount}次,先使用本地数据播放")
|
||||
}
|
||||
if (driverSn.isBlank()) {
|
||||
MediaPlayLogger.printErrorLog("startGetMediaDataSourceLoop:司机屏sn为空,跳过本次查询")
|
||||
mRetryCount++
|
||||
UiThreadHandler.postDelayed(getAdDataSourceLoopRunnable, 1000L)
|
||||
return
|
||||
}
|
||||
if (!NetworkUtils.isConnected()) {
|
||||
MediaPlayLogger.printErrorLog("startGetMediaDataSourceLoop:当前无网络,跳过本次查询")
|
||||
mRetryCount++
|
||||
UiThreadHandler.postDelayed(getAdDataSourceLoopRunnable, 1000L)
|
||||
return
|
||||
}
|
||||
getMediaDataFromMis(object : OchCommonServiceCallback<MediaDataResp> {
|
||||
override fun onSuccess(data: MediaDataResp?) {
|
||||
mHasEverGetMediaDataFromMis = true
|
||||
MediaPlayLogger.printInfoLog(
|
||||
"startGetMediaDataSourceLoop:success, 从管理后台获取到数据,MediaSize=${data?.data?.size}, MediaData=${
|
||||
GsonUtils.toJson(
|
||||
data
|
||||
)
|
||||
}"
|
||||
)
|
||||
val newDataList = MediaDataResp.toMediaItemList(data?.data)
|
||||
// 管理平台如果配置数据为空,不更新
|
||||
if (newDataList.isNotEmpty()) {
|
||||
if (compareMediaDataSource(newDataList)) {
|
||||
updateMediaDataSource(newDataList)
|
||||
MediaPlayLogger.printInfoLog("startGetMediaDataSourceLoop:success, 从管理后台获取到数据,数据有变化更新数据")
|
||||
} else {
|
||||
MediaPlayLogger.printInfoLog("startGetMediaDataSourceLoop:success, 从管理后台获取到数据,数据无变化无需更新")
|
||||
}
|
||||
} else {
|
||||
//请求成功获取到后台配置数据了,但是数据为空,此时为了有内容展示,还是使用本地数据更新播放
|
||||
if (mLastMediaDataSourceList.isEmpty()) {
|
||||
val localAdDataList = getMediaDataFromLocalConfig()
|
||||
updateMediaDataSource(localAdDataList)
|
||||
MediaPlayLogger.printInfoLog("startGetMediaDataSourceLoop:success, 从管理后台获取到数据,返回数据为空,使用本地配置数据播放")
|
||||
}
|
||||
}
|
||||
|
||||
// 获取成功后,延迟5分钟再查询
|
||||
UiThreadHandler.postDelayed(getAdDataSourceLoopRunnable, 5 * 60 * 1000L)
|
||||
MediaPlayLogger.printInfoLog("startGetMediaDataSourceLoop:success, 延迟5分钟后再次检查更新")
|
||||
}
|
||||
|
||||
override fun onFail(code: Int, msg: String?) {
|
||||
MediaPlayLogger.printErrorLog("startGetMediaDataSourceLoop:failed, code=$code, msg=$msg")
|
||||
mRetryCount++
|
||||
val delay = if (mHasEverGetMediaDataFromMis) 5000L else 1000L
|
||||
UiThreadHandler.postDelayed(getAdDataSourceLoopRunnable, delay)
|
||||
MediaPlayLogger.printErrorLog("startGetMediaDataSourceLoop:failed, 延迟${delay / 1000L}秒后再次请求")
|
||||
}
|
||||
|
||||
override fun onError() {
|
||||
super.onError()
|
||||
MediaPlayLogger.printErrorLog("startGetMediaDataSourceLoop:error, 网络异常")
|
||||
mRetryCount++
|
||||
val delay = if (mHasEverGetMediaDataFromMis) 5000L else 1000L
|
||||
UiThreadHandler.postDelayed(getAdDataSourceLoopRunnable, delay)
|
||||
MediaPlayLogger.printErrorLog("startGetMediaDataSourceLoop:error, 网络异常,延迟${delay / 1000L}秒后再次请求")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun removeGetMediaDataSourceLoop() {
|
||||
UiThreadHandler.removeCallbacks(getAdDataSourceLoopRunnable)
|
||||
}
|
||||
|
||||
private fun getMediaDataFromMis(callback: OchCommonServiceCallback<MediaDataResp>) {
|
||||
MediaPlayLogger.printInfoLog("getMediaDataFromMis:准备发送请求,driverSn=$driverSn")
|
||||
mNetworkService.queryMediaDataFromMis(
|
||||
sn = driverSn,
|
||||
screenType = "2",
|
||||
).transformTry().subscribe(OchCommonSubscribeImpl(context, callback, "getMediaDataFromMis"))
|
||||
}
|
||||
|
||||
private fun getMediaDataFromLocalConfig(): List<MediaItem> {
|
||||
val localAdDataList = mutableListOf<MediaItem>()
|
||||
try {
|
||||
val datas: MediaDataList = GsonUtils.fromJson(
|
||||
FunctionBuildConfig.mediaUrlConfig, object : TypeToken<MediaDataList>() {}.type
|
||||
)
|
||||
localAdDataList.addAll(datas.medias)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
MediaPlayLogger.printInfoLog("getMediaDataFromLocalConfig, 获取本地配置数据,dataSize=${localAdDataList.size}")
|
||||
return localAdDataList
|
||||
}
|
||||
|
||||
private fun compareMediaDataSource(newDataList: List<MediaItem>): Boolean {
|
||||
if (mLastMediaDataSourceList.isEmpty() && newDataList.isNotEmpty()) {
|
||||
return true
|
||||
}
|
||||
if (mLastMediaDataSourceList.size != newDataList.size) {
|
||||
return true
|
||||
}
|
||||
try {
|
||||
newDataList.forEachIndexed { index, rotationItem ->
|
||||
val oldIndexItem = mLastMediaDataSourceList[index]
|
||||
if (rotationItem?.fileUrl != oldIndexItem?.fileUrl) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun updateMediaDataSource(newDataList: List<MediaItem>) {
|
||||
mLastMediaDataSourceList.clear()
|
||||
mLastMediaDataSourceList.addAll(newDataList)
|
||||
mMediaDataSourceListenerMap.forEach {
|
||||
val listener = it.value
|
||||
listener.onMediaDataSourceChanged(newDataList)
|
||||
}
|
||||
MediaPlayLogger.printInfoLog("下发新MediaData给监听者,dataSize=${newDataList.size}")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface IMediaDataSourceListener {
|
||||
fun onMediaDataSourceChanged(list: List<MediaItem>)
|
||||
}
|
||||
|
||||
interface IMediaNetworkApi {
|
||||
@Headers("Content-type:application/json;charset=UTF-8")
|
||||
@GET("/platform/biz/adv/screen/advs")
|
||||
fun queryMediaDataFromMis(
|
||||
@Query("sn") sn: String,
|
||||
@Query("screenType") screenType: String
|
||||
): Observable<MediaDataResp>
|
||||
}
|
||||
|
||||
data class MediaData(
|
||||
var id: String?,
|
||||
var title: String?, //素材标题
|
||||
var brand: String?,
|
||||
var file_type: Int = 0, //素材类型: 1 - 视频,2 - 图片
|
||||
var cover_path: String?, //封面图片, 适用 素材类型为视频
|
||||
var file_path: String?, //素材url
|
||||
var descr: String?, //素材描述
|
||||
var apply_screen: Int = 0 //应用屏幕类型: 1司机屏幕 2乘客屏
|
||||
)
|
||||
|
||||
data class MediaDataResp(val data: List<MediaData>) : BaseData() {
|
||||
companion object {
|
||||
fun toMediaItemList(mediaDataList: List<MediaData>?): List<MediaItem> {
|
||||
val rotationItemList = mutableListOf<MediaItem>()
|
||||
mediaDataList?.forEach {
|
||||
val rotationItem = MediaItem(
|
||||
fileUrl = if (TextUtils.isEmpty(it.file_path)) "" else "${it.file_path}",
|
||||
fileType = if (it.file_type == 1) MEDIA_TYPE_VIDEO else MEDIA_TYPE_IMAGE,
|
||||
coverImageUrl = if (TextUtils.isEmpty(it.cover_path)) "" else "${it.cover_path}",
|
||||
title = if (TextUtils.isEmpty(it.title)) "" else "${it.title}"
|
||||
)
|
||||
rotationItemList.add(rotationItem)
|
||||
}
|
||||
return rotationItemList
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.mogo.och.common.module.wigets.media
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.mogo.eagle.core.utilcode.download.DownloadUtils
|
||||
import com.mogo.eagle.core.utilcode.download.callback.IDownloadListener
|
||||
import com.mogo.eagle.core.utilcode.util.EncryptUtils
|
||||
import com.mogo.och.common.module.utils.FileUtils
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* 宣传视频文件本地缓存管理类
|
||||
* 1, 统一了本地缓存文件的存放目录
|
||||
* 2,统一了本地缓存文件的命名,md5编码
|
||||
*/
|
||||
object MediaFileCacheManager {
|
||||
|
||||
const val TAG = "MediaFileCacheManager"
|
||||
|
||||
/**
|
||||
* 创建media缓存文件夹目录(优先放SD卡)
|
||||
*/
|
||||
fun createFileCacheDir(context: Context): Boolean {
|
||||
val cacheDirPath = getFileCacheDir(context)
|
||||
MediaPlayLogger.printInfoLog("createFileCacheDir, dirPath=$cacheDirPath")
|
||||
return com.mogo.eagle.core.utilcode.util.FileUtils.createFileDir(cacheDirPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地缓存文件的文件全路径
|
||||
*/
|
||||
private fun getFileCacheDir(context: Context): String {
|
||||
// 有些手机需要通过自定义目录
|
||||
val relativePath = "mogo" + File.separator + "media" + File.separator
|
||||
val cacheDir = File(Environment.getExternalStorageDirectory(), relativePath)
|
||||
if (com.mogo.eagle.core.utilcode.util.FileUtils.createOrExistsDir(cacheDir)) {
|
||||
return cacheDir.absolutePath
|
||||
}
|
||||
|
||||
return FileUtils.getCacheDirectory(context, "") + relativePath
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地缓存文件的文件名,md5编码避免文件名重复或者特殊字符编码问题
|
||||
*/
|
||||
fun getCacheFileName(mediaUrl: String): String {
|
||||
val fileSuffix = FileUtils.getExtension(mediaUrl)
|
||||
if (TextUtils.isEmpty(fileSuffix)) {
|
||||
Log.e(TAG, "getCacheFileName 根据url获取文件后缀不合法,mediaUrl=$mediaUrl")
|
||||
return ""
|
||||
}
|
||||
return EncryptUtils.encryptMD5ToString(mediaUrl) + FileUtils.EXTENSION_SEPARATOR + fileSuffix
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件缓存的缓存path, 文件名以base64编码避免 中文命名,重复文件名的影响
|
||||
*/
|
||||
fun getCacheFileFullPathByUrl(context: Context, mediaUrl: String): String {
|
||||
return getFileCacheDir(context) + getCacheFileName(mediaUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地是否已经存在下载完成的文件
|
||||
*/
|
||||
fun isLocalCacheFileExists(context: Context, mediaUrl: String): Boolean {
|
||||
val localVideoCacheFilePath =
|
||||
getCacheFileFullPathByUrl(context, mediaUrl)
|
||||
return com.mogo.eagle.core.utilcode.util.FileUtils.isFileExists(localVideoCacheFilePath)
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
*/
|
||||
fun downloadFile(context: Context, mediaUrl: String, listener: IDownloadListener) {
|
||||
val downloadUrl = mediaUrl
|
||||
val downloadDir = getFileCacheDir(context)
|
||||
val downloadFileName = getCacheFileName(mediaUrl)
|
||||
DownloadUtils.downLoad(
|
||||
context,
|
||||
downloadUrl,
|
||||
downloadDir,
|
||||
downloadFileName,
|
||||
listener
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package com.mogo.och.common.module.wigets.media
|
||||
|
||||
import AdvanceImageView
|
||||
import AdvanceVideoView
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.viewpager.widget.PagerAdapter
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
|
||||
import com.mogo.eagle.core.utilcode.util.CountDownTimer
|
||||
import com.mogo.eagle.core.utilcode.util.ToastUtils
|
||||
import com.mogo.och.common.module.wigets.media.MediaLoopPlayView.Companion.IMAGE_COUNT_DOWN_SECONDS
|
||||
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
|
||||
|
||||
class MediaLoopPlayView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : RelativeLayout(context, attrs) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "MediaLoopPlayView"
|
||||
const val IMAGE_COUNT_DOWN_SECONDS = 5
|
||||
}
|
||||
|
||||
private var viewPager: AdvanceViewPager? = null
|
||||
private var pagerAdapter: AdvancePagerAdapter? = null
|
||||
|
||||
init {
|
||||
initView()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
viewPager = AdvanceViewPager(context)
|
||||
pagerAdapter = AdvancePagerAdapter(context, viewPager!!)
|
||||
viewPager?.adapter = pagerAdapter
|
||||
addView(viewPager, LayoutParams(-1, -1))
|
||||
}
|
||||
|
||||
fun setMediaData(list: MutableList<MediaItem>) {
|
||||
pagerAdapter?.setMediaData(list)
|
||||
}
|
||||
|
||||
fun setNewMediaData(list: MutableList<MediaItem>) {
|
||||
pagerAdapter?.setNewMediaData(list)
|
||||
}
|
||||
|
||||
fun setPause() {
|
||||
pagerAdapter?.setPause()
|
||||
}
|
||||
|
||||
fun setResume() {
|
||||
pagerAdapter?.setResume()
|
||||
}
|
||||
}
|
||||
|
||||
class AdvanceViewPager : ViewPager {
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
override fun onTouchEvent(ev: MotionEvent?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class AdvancePagerAdapter(context: Context, viewPager: ViewPager) : PagerAdapter(),
|
||||
ViewPager.OnPageChangeListener {
|
||||
|
||||
private val mContext: Context = context
|
||||
private val mViewPager: ViewPager = viewPager
|
||||
|
||||
private var mDataList = mutableListOf<MediaItem>()
|
||||
private var mItemViewList = mutableListOf<View>()
|
||||
|
||||
//新的数据,在轮播下一次切换的时机完成整体数据的更新
|
||||
private val mNewDataList: MutableList<MediaItem> = mutableListOf()
|
||||
|
||||
private var mLastViewPagerPosition = -1
|
||||
private var mImageCountDownTimer: CountDownTimer? = null
|
||||
|
||||
fun setMediaData(list: MutableList<MediaItem>) {
|
||||
if (list.isEmpty()) {
|
||||
MediaPlayLogger.printInfoLog( "setMediaData, list为空")
|
||||
return
|
||||
}
|
||||
|
||||
mDataList.clear()
|
||||
mDataList.addAll(list)
|
||||
mItemViewList.clear()
|
||||
list.forEach {
|
||||
addItemView(it)
|
||||
}
|
||||
|
||||
mViewPager.addOnPageChangeListener(this)
|
||||
notifyDataSetChanged()
|
||||
mViewPager.currentItem = 0
|
||||
|
||||
if (mItemViewList.size > 0) {
|
||||
startLoopPlay()
|
||||
}
|
||||
}
|
||||
|
||||
fun setNewMediaData(list: MutableList<MediaItem>) {
|
||||
mNewDataList.clear()
|
||||
mNewDataList.addAll(list)
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
return mDataList.size
|
||||
}
|
||||
|
||||
override fun isViewFromObject(view: View, `object`: Any): Boolean {
|
||||
return view === `object`
|
||||
}
|
||||
|
||||
override fun destroyItem(container: ViewGroup, position: Int, obj: Any) {
|
||||
try {
|
||||
container.removeView(obj as View)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
override fun instantiateItem(container: ViewGroup, position: Int): Any {
|
||||
val view: View = mItemViewList[position]
|
||||
container.addView(view)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun getItemPosition(`object`: Any): Int {
|
||||
return POSITION_NONE
|
||||
}
|
||||
|
||||
private fun addItemView(item: MediaItem) {
|
||||
if (item.isImageType()) {
|
||||
val imageView = AdvanceImageView(mContext)
|
||||
imageView.initImageUrlData(item.fileUrl)
|
||||
mItemViewList.add(imageView)
|
||||
} else if (item.isVideoType()) {
|
||||
val videoView = AdvanceVideoView(mContext)
|
||||
videoView.initVideoUrlData(item.fileUrl, item.coverImageUrl)
|
||||
mItemViewList.add(videoView)
|
||||
} else {
|
||||
MediaPlayLogger.printErrorLog( "addItemView 不支持的文件类型:${item.fileType}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始播放循环
|
||||
* 1.如果是视频,开始播放视频,等到播放完成后根据media类型开始播放下一个
|
||||
* 2.如果是图片,展示图片,同时开始timer倒计时,倒计时完时根据media类型开始播放下一个
|
||||
*/
|
||||
private fun startLoopPlay() {
|
||||
val currentPosition = mViewPager.currentItem
|
||||
val currentMediaItem = mDataList[currentPosition]
|
||||
if (mItemViewList[currentPosition] is AdvanceVideoView) {
|
||||
MediaPlayLogger.printInfoLog("startLoopPlay: AdvanceVideoView, url=${currentMediaItem.fileUrl}")
|
||||
val videoView = mItemViewList[currentPosition] as AdvanceVideoView
|
||||
videoView.setThumbImageViewVisible()
|
||||
videoView.startPlayVideo(videoPlayLifecycleCallBack)
|
||||
} else if (mItemViewList[currentPosition] is AdvanceImageView) {
|
||||
MediaPlayLogger.printInfoLog("startLoopPlay: AdvanceImageView, url=${currentMediaItem.fileUrl}")
|
||||
val imageView = mItemViewList[currentPosition] as AdvanceImageView
|
||||
imageView.displayImage()
|
||||
startImageCountDownTimer()
|
||||
} else {
|
||||
MediaPlayLogger.printErrorLog("startLoopPlay 不支持的文件类型:${currentMediaItem.fileType}, url=${currentMediaItem.fileUrl}")
|
||||
}
|
||||
}
|
||||
|
||||
private var videoPlayLifecycleCallBack = object : GSYSampleCallBack() {
|
||||
|
||||
override fun onPrepared(url: String?, vararg objects: Any?) {
|
||||
CallerLogger.d(MediaLoopPlayView.TAG, "onPrepared")
|
||||
}
|
||||
|
||||
override fun onAutoComplete(url: String?, vararg objects: Any?) {
|
||||
CallerLogger.d(MediaLoopPlayView.TAG, "onAutoComplete")
|
||||
playNextItemView(false)
|
||||
}
|
||||
|
||||
override fun onPlayError(url: String?, vararg objects: Any?) {
|
||||
super.onPlayError(url, *objects)
|
||||
CallerLogger.d(MediaLoopPlayView.TAG, "onPlayError, error=${objects}")
|
||||
playNextItemView(true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startImageCountDownTimer() {
|
||||
if (mImageCountDownTimer != null) {
|
||||
mImageCountDownTimer?.cancel()
|
||||
mImageCountDownTimer = null
|
||||
}
|
||||
mImageCountDownTimer = object : CountDownTimer(IMAGE_COUNT_DOWN_SECONDS * 1000L, 1000L) {
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
CallerLogger.d(
|
||||
MediaLoopPlayView.TAG,
|
||||
"mImageCountDownTimer倒计时秒, countDown=${millisUntilFinished / 1000}"
|
||||
)
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
MediaPlayLogger.printInfoLog( "mImageCountDownTimer, 倒计时${IMAGE_COUNT_DOWN_SECONDS}秒, onFinish")
|
||||
playNextItemView(false)
|
||||
}
|
||||
}.start()
|
||||
MediaPlayLogger.printInfoLog("mImageCountDownTimer, 开始倒计时 ${IMAGE_COUNT_DOWN_SECONDS}秒")
|
||||
}
|
||||
|
||||
private fun cancelImageCountDownTimer() {
|
||||
if (mImageCountDownTimer != null) {
|
||||
mImageCountDownTimer?.cancel()
|
||||
mImageCountDownTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前item情况,播放下一个item
|
||||
*/
|
||||
private fun playNextItemView(isOnVideoError: Boolean) {
|
||||
// 在播放完成的时机更新整体数据
|
||||
if (mNewDataList.isNotEmpty()) {
|
||||
setMediaData(mNewDataList)
|
||||
mNewDataList.clear()
|
||||
ToastUtils.showShort("宣传视频数据已更新")
|
||||
MediaPlayLogger.printInfoLog("playNextItemView, 宣传视频数据已更新")
|
||||
return
|
||||
}
|
||||
MediaPlayLogger.printInfoLog("playNextItemView")
|
||||
|
||||
val currentPosition = mViewPager.currentItem
|
||||
val currentMediaItem = mDataList[currentPosition]
|
||||
val currentItemView = mItemViewList[currentPosition]
|
||||
MediaPlayLogger.printInfoLog("playNextItemView, currentPosition=$currentPosition, type=${currentMediaItem.fileType}, url=${currentMediaItem.fileUrl}")
|
||||
if (currentItemView is AdvanceVideoView) {
|
||||
currentItemView.onVideoReset()
|
||||
//videoView.setCacheImageViewVisible()
|
||||
if (isOnVideoError) {
|
||||
currentItemView.clearLocalErrorVideo()
|
||||
}
|
||||
if (mItemViewList.size == 1) {
|
||||
currentItemView.startPlay(currentMediaItem.fileUrl)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (currentPosition == mItemViewList.size - 1) {
|
||||
//已经到最后一个, 从头开始
|
||||
mViewPager.post {
|
||||
mViewPager.setCurrentItem(0, true)
|
||||
}
|
||||
} else {
|
||||
mViewPager.post {
|
||||
mViewPager.setCurrentItem(mViewPager.currentItem + 1, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setPause() {
|
||||
MediaPlayLogger.printInfoLog("${MediaLoopPlayView.TAG}, setPause")
|
||||
if (mItemViewList.size <= 0) {
|
||||
return
|
||||
}
|
||||
val currentPosition = mViewPager.currentItem
|
||||
if (mItemViewList[currentPosition] is AdvanceVideoView) {
|
||||
val videoView = mItemViewList[mViewPager.currentItem] as AdvanceVideoView
|
||||
videoView.setVideoPause()
|
||||
} else if (mItemViewList[currentPosition] is AdvanceImageView) {
|
||||
cancelImageCountDownTimer()
|
||||
}
|
||||
}
|
||||
|
||||
fun setResume() {
|
||||
MediaPlayLogger.printInfoLog("${MediaLoopPlayView.TAG}, setResume")
|
||||
if (mItemViewList.size <= 0) {
|
||||
return
|
||||
}
|
||||
val currentPosition = mViewPager.currentItem
|
||||
if (mItemViewList[currentPosition] is AdvanceVideoView) {
|
||||
val videoView = mItemViewList[mViewPager.currentItem] as AdvanceVideoView
|
||||
videoView.setVideoResume()
|
||||
} else if (mItemViewList[currentPosition] is AdvanceImageView) {
|
||||
startImageCountDownTimer()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
||||
}
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
}
|
||||
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
if (state == 0) { //静止,什么都没做
|
||||
val currentPosition = mViewPager.currentItem
|
||||
CallerLogger.d(
|
||||
MediaLoopPlayView.TAG,
|
||||
"onPageScrollStateChanged, state = $state, currentItem = $currentPosition, lastPosition = $mLastViewPagerPosition"
|
||||
)
|
||||
if (mItemViewList.size > 1) { //多于1,才会循环跳转
|
||||
startLoopPlay()
|
||||
mLastViewPagerPosition = currentPosition
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.mogo.och.common.module.wigets.media
|
||||
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.mogo.commons.utils.MogoAnalyticUtils
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object MediaPlayLogger {
|
||||
const val TAG = "MediaPlayLogger"
|
||||
private const val MEDIA_PLAY_PROCESS_KEY_NODE_LOG =
|
||||
"och.media.play.process.key.node.log"
|
||||
|
||||
fun printInfoLog(msg: String) {
|
||||
CallerLogger.i(TAG, msg)
|
||||
trackEvent("Info", msg)
|
||||
}
|
||||
|
||||
fun printWarnLog(msg: String) {
|
||||
CallerLogger.w(TAG, msg)
|
||||
trackEvent("Warn", msg)
|
||||
}
|
||||
|
||||
fun printErrorLog(msg: String) {
|
||||
CallerLogger.e(TAG, msg)
|
||||
trackEvent("Error", msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报埋点
|
||||
*/
|
||||
private fun trackEvent(level: String, msg: String) {
|
||||
ProcessLifecycleOwner.get().lifecycleScope.launch(Dispatchers.IO) {
|
||||
val map: MutableMap<String, Any> = HashMap()
|
||||
map["level"] = level
|
||||
map["msg"] = msg
|
||||
MogoAnalyticUtils.track(
|
||||
MEDIA_PLAY_PROCESS_KEY_NODE_LOG,
|
||||
map
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
package com.mogo.och.common.module.wigets.video
|
||||
package com.mogo.och.common.module.wigets.media
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.mogo.eagle.core.utilcode.util.BarUtils
|
||||
import com.mogo.och.common.module.R
|
||||
|
||||
class VideoPlayerActivity : AppCompatActivity() {
|
||||
class MediaPlayerActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_video_player)
|
||||
val fragment = VideoPlayerFragment()
|
||||
val fragment = MediaPlayerFragment()
|
||||
supportFragmentManager.beginTransaction().add(R.id.videoPlayerContainer, fragment)
|
||||
.commitAllowingStateLoss()
|
||||
BarUtils.hideStatusBarAndSticky(this.window)
|
||||
@@ -0,0 +1,380 @@
|
||||
import android.content.Context
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.mogo.eagle.core.utilcode.download.callback.IDownloadListener
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.Logger
|
||||
import com.mogo.eagle.core.utilcode.util.FileUtils
|
||||
import com.mogo.eagle.core.utilcode.util.ThreadUtils
|
||||
import com.mogo.eagle.core.utilcode.util.UiThreadHandler
|
||||
import com.mogo.eagle.core.widget.media.video.TextureVideoViewOutlineProvider
|
||||
import com.mogo.och.common.module.R
|
||||
import com.mogo.och.common.module.wigets.media.MediaFileCacheManager
|
||||
import com.mogo.och.common.module.wigets.media.MediaLoopPlayView
|
||||
import com.mogo.och.common.module.wigets.media.MediaPlayLogger
|
||||
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
|
||||
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
|
||||
import com.shuyu.gsyvideoplayer.utils.GSYVideoType
|
||||
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
|
||||
import me.jessyan.autosize.utils.AutoSizeUtils
|
||||
import java.io.File
|
||||
|
||||
class AdvanceVideoView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : RelativeLayout(context, attrs) {
|
||||
|
||||
private var containerLayout: RelativeLayout? = null
|
||||
private var thumbnailImageView: ImageView? = null
|
||||
private var videoPlayerView: AdvanceGSYVideoPlayer? = null
|
||||
|
||||
private var gsyVideoPlayerOptionBuilder: GSYVideoOptionBuilder? = null
|
||||
private var gsyVideoPlayerLifecycleCallback: GSYSampleCallBack? = null
|
||||
|
||||
private var thumbnailImageUrl: String = ""
|
||||
private var videoUrl: String = ""
|
||||
|
||||
private val downListener = object : IDownloadListener {
|
||||
override fun onStart(url: String) {
|
||||
setThumbImageViewVisible()
|
||||
Logger.d(MediaLoopPlayView.TAG, "video play download, onStart")
|
||||
}
|
||||
|
||||
override fun onProgress(url: String, downloaded: Long, total: Long) {
|
||||
val percent = (downloaded * 100 / total).toInt()
|
||||
Logger.d(
|
||||
MediaLoopPlayView.TAG,
|
||||
"video play download, onProgress= ${percent}"
|
||||
)
|
||||
if (percent % 10 == 0) {
|
||||
MediaPlayLogger.printInfoLog("downListener,percent=$percent, downloadUrl=${videoUrl}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinished(url: String, path: String) {
|
||||
Logger.d(MediaLoopPlayView.TAG, "video play download, onFinished = $url")
|
||||
//发现下载工具在断网又连网后,已完成的任务又都下载,跳转播放出现问题
|
||||
if (url == videoUrl) {
|
||||
//下载完成
|
||||
ThreadUtils.runOnUiThread {
|
||||
startPlay(Uri.fromFile(File(path)).toString())
|
||||
}
|
||||
MediaPlayLogger.printInfoLog("download finished, 开始播放,downloadUrl=${videoUrl}")
|
||||
} else {//如果当前文件不存在再次去下载当前的
|
||||
Logger.d(
|
||||
MediaLoopPlayView.TAG,
|
||||
"video play download, onFinished but not current url , currentUrl = $videoUrl"
|
||||
)
|
||||
if (FileUtils.isFileExists(path)) {
|
||||
Logger.d(MediaLoopPlayView.TAG, "video play download, had download, startPlay")
|
||||
ThreadUtils.runOnUiThread {
|
||||
startPlay(Uri.fromFile(File(path)).toString())
|
||||
}
|
||||
MediaPlayLogger.printInfoLog("download finished, 开始播放,downloadUrl=${videoUrl}")
|
||||
} else {
|
||||
startDownLoadVideoFile()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(url: String, error: String?) {
|
||||
Logger.d(MediaLoopPlayView.TAG, "video play download, onError msg=$error")
|
||||
MediaPlayLogger.printErrorLog("download error, 准备重新下载,downloadUrl=${videoUrl}")
|
||||
//出错再次下载
|
||||
startDownLoadVideoFile()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
initView()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
//容器
|
||||
containerLayout = RelativeLayout(context)
|
||||
addView(containerLayout, LayoutParams(-1, -1))
|
||||
|
||||
//缩略图
|
||||
thumbnailImageView = ImageView(context)
|
||||
thumbnailImageView?.scaleType = ImageView.ScaleType.FIT_XY
|
||||
|
||||
//视频播放控件
|
||||
if (videoPlayerView === null) {
|
||||
videoPlayerView = AdvanceGSYVideoPlayer(context)
|
||||
}
|
||||
val videoPlayerViewLayoutParams = LayoutParams(-1, -1)
|
||||
//设置videoview占满父view播放
|
||||
videoPlayerViewLayoutParams.addRule(ALIGN_PARENT_LEFT)
|
||||
videoPlayerViewLayoutParams.addRule(ALIGN_PARENT_RIGHT)
|
||||
videoPlayerViewLayoutParams.addRule(ALIGN_PARENT_TOP)
|
||||
videoPlayerViewLayoutParams.addRule(ALIGN_PARENT_BOTTOM)
|
||||
|
||||
containerLayout?.addView(videoPlayerView, videoPlayerViewLayoutParams)
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
fun initVideoUrlData(videoUrl: String, thumbnailImageUrl: String) {
|
||||
// https://img.zhidaohulian.com/fileServer/online_car_hailing/1676357834634/5.m4v
|
||||
// https://img.zhidaohulian.com/fileServer/online_car_hailing/1676360274126/10.mp4
|
||||
this.videoUrl = videoUrl
|
||||
this.thumbnailImageUrl = thumbnailImageUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始播放逻辑
|
||||
*/
|
||||
fun startPlayVideo(onCompletionListener: GSYSampleCallBack) {
|
||||
//首先根据url检查video是否有已经下载完的本地视频文件
|
||||
gsyVideoPlayerLifecycleCallback = onCompletionListener
|
||||
if (MediaFileCacheManager.isLocalCacheFileExists(context, this.videoUrl)) {
|
||||
val localVideoCacheFilePath = MediaFileCacheManager.getCacheFileFullPathByUrl(
|
||||
context, this.videoUrl
|
||||
)
|
||||
MediaPlayLogger.printInfoLog("本地已经有缓存文件,准备开始播放,videoPath=${localVideoCacheFilePath}")
|
||||
val realUri = Uri.fromFile(File(localVideoCacheFilePath)).toString()
|
||||
setThumbImageViewGone()
|
||||
startPlay(realUri)
|
||||
CallerLogger.d(
|
||||
MediaLoopPlayView.TAG, "播放视频,videoUri=$realUri"
|
||||
)
|
||||
} else {
|
||||
thumbnailImageView?.setImageResource(R.drawable.video_holder)
|
||||
videoPlayerView?.thumbImageView = thumbnailImageView
|
||||
thumbnailImageView?.also {
|
||||
Glide.with(context).asBitmap().load(thumbnailImageUrl)
|
||||
.apply(
|
||||
RequestOptions().useUnlimitedSourceGeneratorsPool(true)
|
||||
.placeholder(R.drawable.video_holder)
|
||||
.error(R.drawable.video_holder)
|
||||
.fallback(R.drawable.video_holder)
|
||||
.centerCrop()
|
||||
)
|
||||
.into(it)
|
||||
}
|
||||
setThumbImageViewVisible()
|
||||
|
||||
startDownLoadVideoFile()
|
||||
MediaPlayLogger.printInfoLog("本地无缓存文件,准备下载,downloadUrl=${this.videoUrl}")
|
||||
}
|
||||
}
|
||||
|
||||
fun setThumbImageViewVisible() {
|
||||
UiThreadHandler.post {
|
||||
videoPlayerView?.setCacheImageViewVisible()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setThumbImageViewGone() {
|
||||
UiThreadHandler.post {
|
||||
videoPlayerView?.setCacheImageViewGone()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startDownLoadVideoFile() {
|
||||
//下载视频,下载成功后再播放
|
||||
MediaPlayLogger.printInfoLog( "startDownLoadVideoFile, downloadUrl=${this.videoUrl}")
|
||||
MediaFileCacheManager.downloadFile(
|
||||
context,
|
||||
videoUrl,
|
||||
downListener
|
||||
)
|
||||
}
|
||||
|
||||
fun clearLocalErrorVideo() {
|
||||
val localVideoPath = MediaFileCacheManager.getCacheFileFullPathByUrl(context, videoUrl)
|
||||
if (FileUtils.isFileExists(localVideoPath)) {
|
||||
FileUtils.delete(localVideoPath)
|
||||
}
|
||||
MediaPlayLogger.printInfoLog( "clearLocalErrorVideo, localPath=${localVideoPath}")
|
||||
}
|
||||
|
||||
fun startPlay(localVideoPath: String?) {
|
||||
try {
|
||||
Logger.d(MediaLoopPlayView.TAG, "startPlay")
|
||||
gsyVideoPlayerOptionBuilder = GSYVideoOptionBuilder()
|
||||
gsyVideoPlayerOptionBuilder
|
||||
?.setUrl(localVideoPath) // "/data/user/0/com.mogo.launcher.f/files/video/"
|
||||
?.setPlayTag(MediaFileCacheManager.getCacheFileName(videoUrl))
|
||||
?.setCacheWithPlay(false)
|
||||
?.setThumbPlay(false)
|
||||
?.build(videoPlayerView)
|
||||
|
||||
videoPlayerView?.isFocusableInTouchMode = false
|
||||
videoPlayerView?.setVideoAllCallBack(gsyVideoPlayerLifecycleCallback)
|
||||
videoPlayerView?.startPlayLogic()
|
||||
} catch (e: Exception) {
|
||||
Logger.e(MediaLoopPlayView.TAG, "startPlay error, msg=${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun onVideoReset() {
|
||||
videoPlayerView?.onVideoReset()
|
||||
gsyVideoPlayerLifecycleCallback = null
|
||||
}
|
||||
|
||||
fun setVideoPause() {
|
||||
videoPlayerView?.onVideoPause()
|
||||
}
|
||||
|
||||
fun setVideoResume() {
|
||||
videoPlayerView?.onVideoResume()
|
||||
}
|
||||
}
|
||||
|
||||
class AdvanceGSYVideoPlayer : StandardGSYVideoPlayer {
|
||||
constructor(context: Context?) : super(context)
|
||||
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
init {
|
||||
hideWidget()
|
||||
GSYVideoType.setShowType(GSYVideoType.SCREEN_MATCH_FULL)
|
||||
GSYVideoType.setRenderType(GSYVideoType.GLSURFACE)
|
||||
}
|
||||
|
||||
override fun hideAllWidget() {
|
||||
Logger.d(MediaLoopPlayView.TAG, "AdvanceGSYVideoPlayer,hideAllWidget")
|
||||
}
|
||||
|
||||
override fun changeUiToNormal() {
|
||||
Logger.d(MediaLoopPlayView.TAG, "AdvanceGSYVideoPlayer,changeUiToNormal-hide")
|
||||
hideWidget()
|
||||
}
|
||||
|
||||
override fun changeUiToPreparingShow() {
|
||||
Logger.d(MediaLoopPlayView.TAG, "AdvanceGSYVideoPlayer,changeUiToPreparingShow-hide")
|
||||
hideWidget()
|
||||
}
|
||||
|
||||
override fun changeUiToPlayingShow() {
|
||||
Logger.d(MediaLoopPlayView.TAG, "AdvanceGSYVideoPlayer,changeUiToPlayingShow")
|
||||
setCacheImageViewGone()
|
||||
}
|
||||
|
||||
override fun changeUiToPauseShow() {
|
||||
Logger.d(MediaLoopPlayView.TAG, "AdvanceGSYVideoPlayer,changeUiToPauseShow-hide")
|
||||
startPlayLogic()
|
||||
}
|
||||
|
||||
override fun changeUiToCompleteShow() {
|
||||
Logger.d(MediaLoopPlayView.TAG, "AdvanceGSYVideoPlayer,changeUiToCompleteShow")
|
||||
setCacheImageViewGone()
|
||||
}
|
||||
|
||||
override fun changeUiToPlayingBufferingShow() {
|
||||
Logger.d(MediaLoopPlayView.TAG, "AdvanceGSYVideoPlayer,changeUiToPlayingBufferingShow")
|
||||
hideWidget()
|
||||
}
|
||||
|
||||
override fun changeUiToError() {
|
||||
Logger.d(MediaLoopPlayView.TAG, "AdvanceGSYVideoPlayer,changeUiToError")
|
||||
hideWidget()
|
||||
}
|
||||
|
||||
private fun hideWidget() {
|
||||
setViewShowState(mBottomContainer, INVISIBLE)
|
||||
setViewShowState(mProgressBar, INVISIBLE)
|
||||
setViewShowState(mCurrentTimeTextView, INVISIBLE)
|
||||
setViewShowState(mTotalTimeTextView, INVISIBLE)
|
||||
setViewShowState(mBottomProgressBar, INVISIBLE)
|
||||
setViewShowState(mBackButton, INVISIBLE)
|
||||
setViewShowState(mStartButton, INVISIBLE)
|
||||
|
||||
setViewShowState(mThumbImageViewLayout, VISIBLE)
|
||||
setViewShowState(mThumbImageView, VISIBLE)
|
||||
|
||||
setViewShowState(mTopContainer, INVISIBLE)
|
||||
setViewShowState(mLoadingProgressBar, INVISIBLE)
|
||||
setViewShowState(mLockScreen, INVISIBLE)
|
||||
|
||||
setIsTouchWiget(false)
|
||||
isFocusableInTouchMode = false
|
||||
}
|
||||
|
||||
fun setCacheImageViewVisible() {
|
||||
Logger.d(MediaLoopPlayView.TAG, "AdvanceGSYVideoPlayer,setCacheImageViewVisible")
|
||||
setViewShowState(mThumbImageViewLayout, VISIBLE)
|
||||
// setViewShowState(mThumbImageView, VISIBLE)
|
||||
}
|
||||
|
||||
fun setCacheImageViewGone() {
|
||||
Logger.d(MediaLoopPlayView.TAG, "AdvanceGSYVideoPlayer,setCacheImageViewGone")
|
||||
setViewShowState(mThumbImageViewLayout, INVISIBLE)
|
||||
// setViewShowState(mThumbImageView, INVISIBLE)
|
||||
}
|
||||
|
||||
//失去焦点声音压低
|
||||
override fun onLossTransientCanDuck() {
|
||||
// setStreamVolume(0.2f)
|
||||
setNeedMute(true)
|
||||
}
|
||||
|
||||
//获取焦点声音恢复
|
||||
override fun onGankAudio() {
|
||||
// setStreamVolume(5.0f)
|
||||
setNeedMute(false)
|
||||
}
|
||||
|
||||
private fun setStreamVolume(percent: Float) {
|
||||
var mAudioManager = mContext?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
var maxVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
|
||||
var volume = (percent * maxVolume).toInt()
|
||||
if (volume < 0) {
|
||||
volume = 0
|
||||
}
|
||||
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0)
|
||||
}
|
||||
|
||||
private fun setNeedMute(isMute: Boolean) {
|
||||
gsyVideoManager?.player?.setNeedMute(isMute)
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
if (!mIfCurrentIsFullscreen) {
|
||||
val dp2px = AutoSizeUtils.dp2px(context, 16f)
|
||||
this.outlineProvider = TextureVideoViewOutlineProvider(dp2px.toFloat())
|
||||
this.clipToOutline = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AdvanceImageView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : RelativeLayout(context, attrs) {
|
||||
|
||||
private var imageView: ImageView? = null
|
||||
private var imageUrl: String = ""
|
||||
|
||||
init {
|
||||
initView()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
imageView = ImageView(context)
|
||||
imageView?.scaleType = ImageView.ScaleType.FIT_XY
|
||||
addView(imageView, LayoutParams(-1, -1))
|
||||
}
|
||||
|
||||
fun initImageUrlData(url: String) {
|
||||
this.imageUrl = url
|
||||
}
|
||||
|
||||
fun displayImage() {
|
||||
imageView?.setImageResource(R.drawable.video_holder)
|
||||
imageView?.also {
|
||||
Glide.with(context).asBitmap().load(imageUrl).apply(
|
||||
RequestOptions().useUnlimitedSourceGeneratorsPool(true)
|
||||
.placeholder(R.drawable.video_holder)
|
||||
.error(R.drawable.video_holder)
|
||||
.fallback(R.drawable.video_holder)
|
||||
.centerCrop()
|
||||
).into(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.mogo.och.common.module.wigets.media
|
||||
|
||||
import com.mogo.commons.mvp.MvpFragment
|
||||
import com.mogo.commons.mvp.Presenter
|
||||
import com.mogo.eagle.core.function.main.MainMoGoApplication
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
|
||||
import com.mogo.eagle.core.utilcode.util.GsonUtils
|
||||
import com.mogo.eagle.core.utilcode.util.UiThreadHandler
|
||||
import com.mogo.och.common.module.R
|
||||
import kotlinx.android.synthetic.main.fragment_video_player.imageVideoRotationView
|
||||
|
||||
/**
|
||||
* @author: wangmingjun
|
||||
* @date: 2022/4/12
|
||||
*/
|
||||
class MediaPlayerFragment :
|
||||
MvpFragment<MediaPlayerFragment?, MediaPlayerPresenter?>() {
|
||||
|
||||
companion object {
|
||||
private val TAG = MediaPlayerFragment::class.java.simpleName
|
||||
}
|
||||
|
||||
private var arrayListOf = mutableListOf<MediaItem>()
|
||||
|
||||
override fun getLayoutId(): Int {
|
||||
return R.layout.fragment_video_player
|
||||
}
|
||||
|
||||
override fun createPresenter(): MediaPlayerPresenter {
|
||||
return MediaPlayerPresenter(this)
|
||||
}
|
||||
|
||||
override fun getTagName(): String {
|
||||
return TAG
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
MediaFileCacheManager.createFileCacheDir(MainMoGoApplication.getApp().applicationContext)
|
||||
MediaDataSourceManager.init(TAG, object : IMediaDataSourceListener {
|
||||
override fun onMediaDataSourceChanged(list: List<MediaItem>) {
|
||||
val isNewData = arrayListOf.isNotEmpty()
|
||||
CallerLogger.d(
|
||||
TAG, "onMediaDataSourceChanged:isNewData=$isNewData, list=${GsonUtils.toJson(list)}"
|
||||
)
|
||||
arrayListOf.clear()
|
||||
arrayListOf.addAll(list)
|
||||
UiThreadHandler.post {
|
||||
if (isNewData) {
|
||||
imageVideoRotationView.setNewMediaData(arrayListOf)
|
||||
} else {
|
||||
imageVideoRotationView.setMediaData(arrayListOf)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
imageVideoRotationView.setPause()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
imageVideoRotationView.setResume()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
MediaDataSourceManager.unInit(TAG)
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
|
||||
class MediaPlayerPresenter(view: MediaPlayerFragment?) :
|
||||
Presenter<MediaPlayerFragment?>(view)
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.mogo.och.common.module.wigets.video
|
||||
|
||||
data class AdsDatas(val ads:MutableList<RotationItem>)
|
||||
|
||||
data class RotationItem(
|
||||
var path: String,
|
||||
var type: Int,
|
||||
var cacheImgPath: String,
|
||||
var title: String
|
||||
)
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.mogo.och.common.module.wigets.video
|
||||
|
||||
import AdvancePagerAdapter
|
||||
import AdvanceViewPager
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.RelativeLayout
|
||||
|
||||
class ImageVideoRotationView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : RelativeLayout(context, attrs) {
|
||||
|
||||
private var viewPager: AdvanceViewPager? = null
|
||||
private var pagerAdapter: AdvancePagerAdapter? = null
|
||||
|
||||
companion object {
|
||||
const val TAG = "ImageAndVideoRotation"
|
||||
}
|
||||
|
||||
init {
|
||||
initView()
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun initView() {
|
||||
viewPager = AdvanceViewPager(context)
|
||||
pagerAdapter = AdvancePagerAdapter(context, viewPager!!)
|
||||
viewPager?.adapter = pagerAdapter
|
||||
addView(viewPager, LayoutParams(-1, -1))
|
||||
}
|
||||
|
||||
fun setData(list: MutableList<RotationItem>) {
|
||||
pagerAdapter?.setData(list)
|
||||
}
|
||||
|
||||
fun setPause() {
|
||||
pagerAdapter?.setPause()
|
||||
}
|
||||
|
||||
fun setResume() {
|
||||
pagerAdapter?.setResume()
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package com.mogo.och.common.module.wigets.video
|
||||
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.mogo.commons.mvp.MvpFragment
|
||||
import com.mogo.commons.mvp.Presenter
|
||||
import com.mogo.eagle.core.data.config.FunctionBuildConfig
|
||||
import com.mogo.eagle.core.utilcode.util.GsonUtils
|
||||
import com.mogo.och.common.module.R
|
||||
import kotlinx.android.synthetic.main.fragment_video_player.*
|
||||
|
||||
/**
|
||||
* @author: wangmingjun
|
||||
* @date: 2022/4/12
|
||||
*/
|
||||
class VideoPlayerFragment :
|
||||
MvpFragment<VideoPlayerFragment?, VideoPlayerPresenter?>() {
|
||||
|
||||
private var arrayListOf = mutableListOf<RotationItem>()
|
||||
|
||||
override fun getLayoutId(): Int {
|
||||
return R.layout.fragment_video_player
|
||||
}
|
||||
|
||||
override fun createPresenter(): VideoPlayerPresenter {
|
||||
return VideoPlayerPresenter(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = VideoPlayerFragment::class.java.simpleName
|
||||
}
|
||||
|
||||
override fun getTagName(): String {
|
||||
return TAG
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
initResourceData()
|
||||
imageVideoRotationView.setData(arrayListOf)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
imageVideoRotationView.setPause()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
imageVideoRotationView.setResume()
|
||||
}
|
||||
|
||||
private fun initResourceData() {
|
||||
try {
|
||||
arrayListOf.clear()
|
||||
var datas: AdsDatas = GsonUtils.fromJson(FunctionBuildConfig.tempConfig,object : TypeToken<AdsDatas>() {}.type)
|
||||
arrayListOf.addAll(datas.ads)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VideoPlayerPresenter(view: VideoPlayerFragment?) :
|
||||
Presenter<VideoPlayerFragment?>(view)
|
||||
@@ -1,589 +0,0 @@
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.viewpager.widget.PagerAdapter
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.mogo.eagle.core.utilcode.download.*
|
||||
import com.mogo.eagle.core.utilcode.download.callback.*
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.Logger
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
|
||||
import com.mogo.eagle.core.utilcode.util.CountDownTimer
|
||||
import com.mogo.eagle.core.utilcode.util.FileUtils
|
||||
import com.mogo.eagle.core.utilcode.util.ThreadUtils
|
||||
import com.mogo.eagle.core.utilcode.util.UiThreadHandler
|
||||
import com.mogo.och.common.module.R
|
||||
import com.mogo.och.common.module.wigets.video.ImageVideoRotationView
|
||||
import com.mogo.och.common.module.wigets.video.RotationItem
|
||||
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
|
||||
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
|
||||
import com.shuyu.gsyvideoplayer.utils.Debuger
|
||||
import com.shuyu.gsyvideoplayer.utils.GSYVideoType
|
||||
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
|
||||
import java.io.File
|
||||
|
||||
class AdvanceVideoView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : RelativeLayout(context, attrs) {
|
||||
|
||||
private var videoRelativeLayout: RelativeLayout? = null
|
||||
private var cacheImage: ImageView? = null
|
||||
private var videoViewPlayer: AdvanceGSYVideoPlayer? = null
|
||||
private var gsyVideoOptionBuilder: GSYVideoOptionBuilder? = null
|
||||
private var mOnCompletionListener: GSYSampleCallBack? = null
|
||||
private var downloadVideoName = ""
|
||||
private var fileNetPath: String? = ""
|
||||
private var cacheImageUrl: String? = ""
|
||||
private var mVideoDirPath: String? = ""
|
||||
|
||||
init {
|
||||
mVideoDirPath = context.filesDir.absolutePath + File.separator + "video" + File.separator
|
||||
initView()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
initCacheImgView()
|
||||
initVideoView()
|
||||
}
|
||||
|
||||
private fun initCacheImgView() {
|
||||
cacheImage = ImageView(context)
|
||||
cacheImage?.scaleType = ImageView.ScaleType.FIT_XY
|
||||
}
|
||||
|
||||
private fun initVideoView() {
|
||||
videoRelativeLayout = RelativeLayout(context)
|
||||
addView(videoRelativeLayout, LayoutParams(-1, -1))
|
||||
|
||||
if (videoViewPlayer === null) {
|
||||
//视频播放控件
|
||||
videoViewPlayer = AdvanceGSYVideoPlayer(context)
|
||||
}
|
||||
|
||||
var layoutParams = LayoutParams(-1, -1)
|
||||
//设置videoview占满父view播放
|
||||
layoutParams.addRule(ALIGN_PARENT_LEFT)
|
||||
layoutParams.addRule(ALIGN_PARENT_RIGHT)
|
||||
layoutParams.addRule(ALIGN_PARENT_TOP)
|
||||
layoutParams.addRule(ALIGN_PARENT_BOTTOM)
|
||||
|
||||
videoRelativeLayout?.addView(videoViewPlayer, layoutParams)
|
||||
}
|
||||
|
||||
fun setVideoPath(path: String, cacheImageUrl: String) {
|
||||
// https://img.zhidaohulian.com/fileServer/online_car_hailing/1676357834634/5.m4v
|
||||
// https://img.zhidaohulian.com/fileServer/online_car_hailing/1676360274126/10.mp4
|
||||
this.fileNetPath = path
|
||||
this.cacheImageUrl = cacheImageUrl
|
||||
val pathList = path.split("/")
|
||||
if (pathList.isNotEmpty()) {
|
||||
this.downloadVideoName = pathList[pathList.size - 1]
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadCacheImg() {
|
||||
videoViewPlayer?.thumbImageView = cacheImage
|
||||
cacheImage?.setImageResource(R.drawable.video_holder)
|
||||
// setCacheImageViewVisible()
|
||||
Logger.d(ImageVideoRotationView.TAG, "setVideoPath")
|
||||
cacheImage?.let {
|
||||
Glide.with(context).asBitmap().load(cacheImageUrl)
|
||||
.apply(
|
||||
RequestOptions().useUnlimitedSourceGeneratorsPool(true)
|
||||
.placeholder(R.drawable.video_holder)
|
||||
.error(R.drawable.video_holder)
|
||||
.fallback(R.drawable.video_holder)
|
||||
.centerCrop()
|
||||
)
|
||||
.into(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearLocalErrorVideo() {
|
||||
if (downloadVideoName.isNotEmpty()
|
||||
&& FileUtils.isFileExists(mVideoDirPath + downloadVideoName)
|
||||
) {
|
||||
FileUtils.delete(mVideoDirPath + downloadVideoName)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
fun setCacheImageViewVisible() {
|
||||
UiThreadHandler.post {
|
||||
videoViewPlayer?.setCacheImageViewVisible()
|
||||
}
|
||||
}
|
||||
|
||||
fun setCacheImageViewGone() {
|
||||
UiThreadHandler.post {
|
||||
videoViewPlayer?.setCacheImageViewGone()
|
||||
}
|
||||
}
|
||||
|
||||
fun setVideo(onCompletionListener: GSYSampleCallBack) {
|
||||
loadCacheImg()
|
||||
Logger.d(ImageVideoRotationView.TAG, "setVideo")
|
||||
mOnCompletionListener = onCompletionListener
|
||||
//判断是否已经下载
|
||||
if (downloadVideoName.isNotEmpty()) {
|
||||
Logger.d(
|
||||
ImageVideoRotationView.TAG,
|
||||
"video local url = $mVideoDirPath$downloadVideoName"
|
||||
)
|
||||
if (FileUtils.isFileExists(mVideoDirPath + downloadVideoName)) {
|
||||
Logger.d(ImageVideoRotationView.TAG, "have cache startPlay")
|
||||
startPlay(Uri.fromFile(File(mVideoDirPath + downloadVideoName)).toString())
|
||||
return
|
||||
}
|
||||
startDownLoadVideo()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startDownLoadVideo() {
|
||||
//下载视频, 下载成功后再播放
|
||||
Logger.d(ImageVideoRotationView.TAG, "startDownLoadVideo")
|
||||
FileUtils.createFileDir(mVideoDirPath)
|
||||
DownloadUtils.downLoad(
|
||||
context, fileNetPath!!, mVideoDirPath!!, downloadVideoName, downListener)
|
||||
}
|
||||
|
||||
fun startPlay(localVideoPath: String?) {
|
||||
try {
|
||||
Logger.d(ImageVideoRotationView.TAG, "startPlay")
|
||||
gsyVideoOptionBuilder = GSYVideoOptionBuilder()
|
||||
gsyVideoOptionBuilder
|
||||
?.setUrl(
|
||||
localVideoPath
|
||||
) // "/data/user/0/com.mogo.launcher.f/files/video/"
|
||||
?.setPlayTag(downloadVideoName)
|
||||
?.setCacheWithPlay(false)
|
||||
?.setThumbPlay(false)
|
||||
?.build(videoViewPlayer)
|
||||
|
||||
videoViewPlayer?.isFocusableInTouchMode = false
|
||||
videoViewPlayer?.setVideoAllCallBack(mOnCompletionListener)
|
||||
videoViewPlayer?.startPlayLogic()
|
||||
} catch (e: Exception) {
|
||||
Logger.d(ImageVideoRotationView.TAG, "startPlay e = ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun onVideoReset() {
|
||||
videoViewPlayer?.onVideoReset()
|
||||
mOnCompletionListener = null
|
||||
}
|
||||
|
||||
fun setPause() {
|
||||
if (videoViewPlayer !== null) {
|
||||
videoViewPlayer?.onVideoPause()
|
||||
}
|
||||
}
|
||||
|
||||
fun setResume() {
|
||||
if (videoViewPlayer !== null) {
|
||||
videoViewPlayer?.onVideoResume()
|
||||
}
|
||||
}
|
||||
|
||||
private val downListener = object : IDownloadListener {
|
||||
|
||||
override fun onStart(url: String) {
|
||||
setCacheImageViewVisible()
|
||||
Logger.d(ImageVideoRotationView.TAG, "download-onStart")
|
||||
}
|
||||
|
||||
override fun onProgress(url: String, downloaded: Long, total: Long) {
|
||||
Logger.d(ImageVideoRotationView.TAG, "download-onProgress== ${ (downloaded * 100 / total).toInt() }")
|
||||
}
|
||||
|
||||
override fun onFinished(url: String, path: String) {
|
||||
Logger.d(ImageVideoRotationView.TAG, "download-onFinished = $url")
|
||||
if (url == fileNetPath) { //发现下载工具在断网又连网后,已完成的任务又都下载,跳转播放出现问题
|
||||
//下载完成
|
||||
ThreadUtils.runOnUiThread {
|
||||
startPlay(Uri.fromFile(File(path)).toString())
|
||||
}
|
||||
} else {//如果当前文件不存在再次去下载当前的
|
||||
Logger.d(
|
||||
ImageVideoRotationView.TAG, "download-onFinished = not current" +
|
||||
",currentUrl = $fileNetPath "
|
||||
)
|
||||
if (FileUtils.isFileExists(path)) {
|
||||
Logger.d(ImageVideoRotationView.TAG, "have download startPlay")
|
||||
ThreadUtils.runOnUiThread {
|
||||
startPlay(Uri.fromFile(File(path)).toString())
|
||||
}
|
||||
return
|
||||
} else {
|
||||
startDownLoadVideo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(url: String, error: String?) {
|
||||
Logger.d(ImageVideoRotationView.TAG, "download-onError-$error")
|
||||
//出错再次下载
|
||||
startDownLoadVideo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AdvanceViewPager : ViewPager {
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
override fun onTouchEvent(ev: MotionEvent?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class AdvancePagerAdapter(context: Context, viewPager: ViewPager) : PagerAdapter(),
|
||||
ViewPager.OnPageChangeListener {
|
||||
private val mContext: Context = context
|
||||
private val mViewPager: ViewPager = viewPager
|
||||
|
||||
private var dataList = mutableListOf<RotationItem>()
|
||||
private var viewList = mutableListOf<View>()
|
||||
|
||||
private var lastPosition = -1
|
||||
|
||||
private var current = 0
|
||||
private val time = 5000
|
||||
private var pause = false
|
||||
private var countDownTimer: CountDownTimer? = null
|
||||
|
||||
fun setData(list: MutableList<RotationItem>) {
|
||||
if (list.isEmpty()) return
|
||||
dataList.addAll(list)
|
||||
viewList.clear()
|
||||
list.forEach {
|
||||
addView(it)
|
||||
}
|
||||
|
||||
mViewPager.addOnPageChangeListener(this)
|
||||
notifyDataSetChanged()
|
||||
mViewPager.currentItem = 0
|
||||
|
||||
if (viewList.size > 0) {
|
||||
if (viewList[mViewPager.currentItem] is AdvanceVideoView) {//有人反应第一个是视频不播放这边优化了一下
|
||||
Logger.d(ImageVideoRotationView.TAG, "第一个是视频")
|
||||
val video = viewList[mViewPager.currentItem] as AdvanceVideoView
|
||||
video.setVideo(gsySampleCallBack)
|
||||
} else if (viewList[mViewPager.currentItem] is AdvanceImageView) {
|
||||
Logger.d(ImageVideoRotationView.TAG, "startTimer()_1")
|
||||
current = 0//换页重新计算时间
|
||||
startTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
return dataList.size
|
||||
}
|
||||
|
||||
override fun isViewFromObject(view: View, `object`: Any): Boolean {
|
||||
return view === `object`
|
||||
}
|
||||
|
||||
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
|
||||
container.removeView(viewList[position])
|
||||
}
|
||||
|
||||
override fun instantiateItem(container: ViewGroup, position: Int): Any {
|
||||
val view: View = viewList[position]
|
||||
container.addView(view)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun getItemPosition(`object`: Any): Int {
|
||||
return POSITION_NONE
|
||||
}
|
||||
|
||||
private fun addView(item: RotationItem) {
|
||||
if (item.type == 1) { // 表示视频
|
||||
val videoView = AdvanceVideoView(mContext)
|
||||
videoView.setVideoPath(item.path, item.cacheImgPath)
|
||||
viewList.add(videoView)
|
||||
} else { // 表示图片
|
||||
val imageView = AdvanceImageView(mContext)
|
||||
imageView.setImagePath(item.path)
|
||||
viewList.add(imageView)
|
||||
}
|
||||
}
|
||||
|
||||
fun setPause() {
|
||||
pause = true
|
||||
if (viewList.size > 0 && viewList[mViewPager.currentItem] is AdvanceVideoView) {
|
||||
val videoView = viewList[mViewPager.currentItem] as AdvanceVideoView
|
||||
videoView.setPause()
|
||||
}
|
||||
}
|
||||
|
||||
fun setResume() {
|
||||
pause = false
|
||||
if (viewList.size > 0 && viewList[mViewPager.currentItem] is AdvanceVideoView) {
|
||||
val videoView = viewList[mViewPager.currentItem] as AdvanceVideoView
|
||||
videoView.setResume()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
||||
}
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
}
|
||||
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
// 由于viewpager的预加载机制onPageSelected这里面加载videoview 放的跟玩一样 等操作完成后再播放videoview就香了 很丝滑
|
||||
if (state == 0) { //静止,什么都没做
|
||||
val currentItem = mViewPager.currentItem
|
||||
Logger.d(
|
||||
ImageVideoRotationView.TAG,
|
||||
"state = $state currentItem = $currentItem lastPosition = $lastPosition"
|
||||
)
|
||||
if (viewList.size > 1) { //多于1,才会循环跳转
|
||||
if (viewList[mViewPager.currentItem] is AdvanceVideoView) {
|
||||
val videoView = (viewList[mViewPager.currentItem] as AdvanceVideoView)
|
||||
videoView.setCacheImageViewVisible()
|
||||
videoView.setVideo(gsySampleCallBack)
|
||||
} else if (viewList[mViewPager.currentItem] is AdvanceImageView) {
|
||||
Logger.d(ImageVideoRotationView.TAG, "startTimer()")
|
||||
current = 0//换页重新计算时间
|
||||
startTimer()
|
||||
}
|
||||
lastPosition = mViewPager.currentItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var gsySampleCallBack = object : GSYSampleCallBack() {
|
||||
|
||||
override fun onPrepared(url: String?, vararg objects: Any?) {
|
||||
Logger.d(ImageVideoRotationView.TAG, "onPrepared")
|
||||
// if (viewList[mViewPager.currentItem] is AdvanceVideoView) {
|
||||
// val videoView = (viewList[mViewPager.currentItem] as AdvanceVideoView)
|
||||
// UiThreadHandler.postDelayed(Runnable {
|
||||
// videoView.setCacheImageViewGone()
|
||||
// }, 1000)
|
||||
// }
|
||||
}
|
||||
|
||||
override fun onAutoComplete(url: String?, vararg objects: Any?) {
|
||||
Logger.d(ImageVideoRotationView.TAG, "onAutoComplete()")
|
||||
if (viewList[mViewPager.currentItem] is AdvanceVideoView) {
|
||||
val videoView = (viewList[mViewPager.currentItem] as AdvanceVideoView)
|
||||
videoView.onVideoReset()
|
||||
if (viewList.size == 1){
|
||||
videoView.startPlay(url)
|
||||
}else{
|
||||
goNextItemView()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPlayError(url: String?, vararg objects: Any?) {
|
||||
super.onPlayError(url, *objects)
|
||||
Logger.d(ImageVideoRotationView.TAG, "onPlayError()-${objects}")
|
||||
if (viewList[mViewPager.currentItem] is AdvanceVideoView) {
|
||||
val videoView = (viewList[mViewPager.currentItem] as AdvanceVideoView)
|
||||
videoView.onVideoReset()
|
||||
// videoView.setCacheImageViewVisible()
|
||||
videoView.clearLocalErrorVideo()
|
||||
goNextItemView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startTimer() {
|
||||
if (countDownTimer != null) {
|
||||
countDownTimer?.cancel()
|
||||
countDownTimer = null
|
||||
}
|
||||
countDownTimer = object : CountDownTimer(5000, 1000) {
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
CallerLogger.d(
|
||||
SceneConstant.M_BUS_P + "startTimer",
|
||||
"倒计时秒 = ${millisUntilFinished / 1000}"
|
||||
)
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
CallerLogger.d(ImageVideoRotationView.TAG + "startTimer", "5s到,跳转")
|
||||
goNextItemView()
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
/**
|
||||
* view 跳转
|
||||
*/
|
||||
private fun goNextItemView() {
|
||||
if (mViewPager.currentItem == viewList.size - 1) {//已经到最后一个
|
||||
mViewPager.post {
|
||||
mViewPager.setCurrentItem(0, true)
|
||||
}
|
||||
} else {
|
||||
mViewPager.post {
|
||||
mViewPager.setCurrentItem(mViewPager.currentItem + 1, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AdvanceImageView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : RelativeLayout(context, attrs) {
|
||||
|
||||
private var imageView: ImageView? = null
|
||||
|
||||
init {
|
||||
initView()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
imageView = ImageView(context)
|
||||
imageView?.scaleType = ImageView.ScaleType.FIT_XY
|
||||
addView(imageView, LayoutParams(-1, -1))
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
fun setImagePath(path: String) {
|
||||
imageView?.setImageResource(R.drawable.video_holder)
|
||||
imageView?.let {
|
||||
Glide.with(context).asBitmap().load(path)
|
||||
.apply(
|
||||
RequestOptions().useUnlimitedSourceGeneratorsPool(true)
|
||||
.placeholder(R.drawable.video_holder)
|
||||
.error(R.drawable.video_holder)
|
||||
.fallback(R.drawable.video_holder)
|
||||
.centerCrop()
|
||||
)
|
||||
.into(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AdvanceGSYVideoPlayer : StandardGSYVideoPlayer {
|
||||
constructor(context: Context?) : super(context)
|
||||
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
init {
|
||||
hideWidget()
|
||||
GSYVideoType.setShowType(GSYVideoType.SCREEN_MATCH_FULL)
|
||||
GSYVideoType.setRenderType(GSYVideoType.GLSURFACE)
|
||||
}
|
||||
|
||||
override fun hideAllWidget() {
|
||||
Logger.d("ImageAndVideoRotation", "hideAllWidget")
|
||||
}
|
||||
|
||||
override fun changeUiToNormal() {
|
||||
Logger.d("ImageAndVideoRotation", "changeUiToNormal-hide")
|
||||
hideWidget()
|
||||
}
|
||||
|
||||
override fun changeUiToPreparingShow() {
|
||||
Logger.d("ImageAndVideoRotation", "changeUiToPreparingShow-hide")
|
||||
hideWidget()
|
||||
}
|
||||
|
||||
override fun changeUiToPlayingShow() {
|
||||
Logger.d("ImageAndVideoRotation", "changeUiToPlayingShow")
|
||||
setCacheImageViewGone()
|
||||
}
|
||||
|
||||
override fun changeUiToPauseShow() {
|
||||
Logger.d("ImageAndVideoRotation", "changeUiToPauseShow-hide")
|
||||
startPlayLogic()
|
||||
}
|
||||
|
||||
override fun changeUiToCompleteShow() {
|
||||
Logger.d("ImageAndVideoRotation", "changeUiToCompleteShow")
|
||||
setCacheImageViewGone()
|
||||
}
|
||||
|
||||
override fun changeUiToPlayingBufferingShow() {
|
||||
Logger.d("ImageAndVideoRotation", "changeUiToPlayingBufferingShow -hide")
|
||||
hideWidget()
|
||||
}
|
||||
|
||||
override fun changeUiToError() {
|
||||
Logger.d("ImageAndVideoRotation", "changeUiToError-hide")
|
||||
hideWidget()
|
||||
}
|
||||
|
||||
private fun hideWidget(){
|
||||
setViewShowState(mBottomContainer, INVISIBLE)
|
||||
setViewShowState(mProgressBar, INVISIBLE)
|
||||
setViewShowState(mCurrentTimeTextView, INVISIBLE)
|
||||
setViewShowState(mTotalTimeTextView, INVISIBLE)
|
||||
setViewShowState(mBottomProgressBar, INVISIBLE)
|
||||
setViewShowState(mBackButton, INVISIBLE)
|
||||
setViewShowState(mStartButton, INVISIBLE)
|
||||
|
||||
setViewShowState(mThumbImageViewLayout, VISIBLE)
|
||||
setViewShowState(mThumbImageView, VISIBLE)
|
||||
|
||||
setViewShowState(mTopContainer, INVISIBLE)
|
||||
|
||||
setViewShowState(mLoadingProgressBar, INVISIBLE)
|
||||
setViewShowState(
|
||||
mLockScreen, INVISIBLE
|
||||
)
|
||||
|
||||
setIsTouchWiget(false)
|
||||
isFocusableInTouchMode = false
|
||||
}
|
||||
|
||||
fun setCacheImageViewVisible() {
|
||||
Logger.d("ImageAndVideoRotation", "CacheImageViewVISIBLE")
|
||||
setViewShowState(mThumbImageViewLayout, VISIBLE)
|
||||
// setViewShowState(mThumbImageView, VISIBLE)
|
||||
}
|
||||
|
||||
fun setCacheImageViewGone() {
|
||||
Logger.d("ImageAndVideoRotation", "CacheImageViewGONE")
|
||||
setViewShowState(mThumbImageViewLayout, INVISIBLE)
|
||||
// setViewShowState(mThumbImageView, INVISIBLE)
|
||||
}
|
||||
|
||||
//失去焦点声音压低
|
||||
override fun onLossTransientCanDuck() {
|
||||
// setStreamVolume(0.2f)
|
||||
setNeedMute(true)
|
||||
}
|
||||
|
||||
//获取焦点声音恢复
|
||||
override fun onGankAudio() {
|
||||
// setStreamVolume(5.0f)
|
||||
setNeedMute(false)
|
||||
}
|
||||
|
||||
private fun setStreamVolume(percent: Float){
|
||||
var mAudioManager = mContext?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
var maxVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
|
||||
var volume = (percent * maxVolume).toInt()
|
||||
if (volume < 0 ){
|
||||
volume = 0
|
||||
}
|
||||
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,volume,0)
|
||||
}
|
||||
|
||||
private fun setNeedMute(isMute: Boolean){
|
||||
gsyVideoManager?.player?.setNeedMute(isMute)
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,9 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- 图片或视频广告-->
|
||||
<com.mogo.och.common.module.wigets.video.ImageVideoRotationView
|
||||
<com.mogo.och.common.module.wigets.media.MediaLoopPlayView
|
||||
android:id="@+id/imageVideoRotationView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Reference in New Issue
Block a user