diff --git a/app/build.gradle b/app/build.gradle index d2a4322a89..ad59d0593d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' // kotlin apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' - +apply plugin: "com.gh.gamecenter.plugin" import groovy.xml.XmlUtil diff --git a/app/proguard-rules.txt b/app/proguard-rules.txt index be871ae960..d687010dff 100644 --- a/app/proguard-rules.txt +++ b/app/proguard-rules.txt @@ -143,6 +143,7 @@ -keep class com.gh.gamecenter.qa.entity.** {*;} -keep class com.gh.gamecenter.retrofit.** {*;} -keep class com.gh.gamecenter.eventbus.** {*;} +-keep class com.gh.gamecenter.video.detail.** {*;} -keep class * extends rx.Subscriber #---------------------------------webview------------------------------------ diff --git a/app/src/main/java/com/gh/common/constant/Constants.java b/app/src/main/java/com/gh/common/constant/Constants.java index e31104e54c..532854a001 100644 --- a/app/src/main/java/com/gh/common/constant/Constants.java +++ b/app/src/main/java/com/gh/common/constant/Constants.java @@ -106,8 +106,6 @@ public class Constants { public static final String SP_FILTER_TAGS = "filter_tags"; //实名认证弹窗分类数据 public static final String SP_AUTH_DIALOG = "auth_dialog"; - //顶部视频进度保存,重启恢复 - public static final String SP_TOP_VIDEO_SCHEDULE = "top_video_schedule"; //我的光环小红点提示 public static final String SP_GH_RED_POINT_REMIND = "gh_red_point_remind"; //论坛首页引导 @@ -137,6 +135,9 @@ public class Constants { public static final String SP_XAPK_UNZIP_ACTIVITY = "xapk_unzip_activity"; public static final String SP_XAPK_URL = "xapk_url"; + //首页视频播放进度 + public static final String SP_HOME_VIDEO_PLAY_RECORD = "home_video_play_record"; + // 用户是否曾经永久拒绝过存储权限 public static final String SP_USER_HAS_PERMANENTLY_DENIED_STORAGE_PERMISSION = "user_has_permanently_denied_storage_permission"; diff --git a/app/src/main/java/com/gh/common/util/LogUtils.java b/app/src/main/java/com/gh/common/util/LogUtils.java index 28c884de78..b58dbe5f81 100644 --- a/app/src/main/java/com/gh/common/util/LogUtils.java +++ b/app/src/main/java/com/gh/common/util/LogUtils.java @@ -377,6 +377,63 @@ public class LogUtils { uploadVideoStreaming(object); } + public static void uploadTopVideoStreamingPlaying(String action, String videoId, String videoTitle, String gameId, String gameName, String videoModel, String videoPlayStatus, double videoSize, int videoTotalTime, int videoPlayTs, int progress) { + JSONObject object = new JSONObject(); + JSONObject payloadObject = new JSONObject(); + try { + object.put("event", "TOP_VIDEO_PLAYING"); + object.put("action", action); + + payloadObject.put("video_mode", videoModel);//视频播放方式 ["自动播放"/"点击播放"/"全屏播放"] + payloadObject.put("video_id", videoId); + payloadObject.put("video_title", videoTitle); + payloadObject.put("game_id", gameId); + payloadObject.put("game_name", gameName); + payloadObject.put("video_size", videoSize); + payloadObject.put("video_total_time", videoTotalTime); + payloadObject.put("progress", progress); + payloadObject.put("video_play_ts", videoPlayTs); + payloadObject.put("video_play_status", videoPlayStatus); + + object.put("payload", payloadObject); + object.put("meta", getMetaObject()); + } catch (JSONException e) { + e.printStackTrace(); + } + if (BuildConfig.DEBUG) { + Utils.log("LogUtils->" + object.toString()); + } + LoghubUtils.log(object, "video_streaming", false); + } + + public static void uploadHomeVideoStreamingPlaying(String action, boolean videoShade, String videoId, String videoTitle, String gameId, String gameName, double videoSize, int videoTotalTime, int videoPlayTs, int progress) { + JSONObject object = new JSONObject(); + JSONObject payloadObject = new JSONObject(); + try { + object.put("event", "HOME_VIDEO_PLAYING"); + object.put("action", action); + + payloadObject.put("video_id", videoId); + payloadObject.put("video_title", videoTitle); + payloadObject.put("game_id", gameId); + payloadObject.put("game_name", gameName); + payloadObject.put("video_size", videoSize); + payloadObject.put("video_total_time", videoTotalTime); + payloadObject.put("progress", progress); + payloadObject.put("video_play_ts", videoPlayTs); + payloadObject.put("video_shade", String.valueOf(videoShade));//["true"/"false"]是否存在引导遮罩 + + object.put("payload", payloadObject); + object.put("meta", getMetaObject()); + } catch (JSONException e) { + e.printStackTrace(); + } + if (BuildConfig.DEBUG) { + Utils.log("LogUtils->" + object.toString()); + } + LoghubUtils.log(object, "video_streaming", false); + } + public static void uploadWelcomeDialog(String action, String dialogId, String linkTitle) { ExposureEntity payload = new ExposureEntity(); payload.setWelcomeDialogId(dialogId); diff --git a/app/src/main/java/com/gh/common/view/DrawableView.kt b/app/src/main/java/com/gh/common/view/DrawableView.kt index 4ee6f10dca..4811b1f93c 100644 --- a/app/src/main/java/com/gh/common/view/DrawableView.kt +++ b/app/src/main/java/com/gh/common/view/DrawableView.kt @@ -2,24 +2,21 @@ package com.gh.common.view import android.content.res.ColorStateList import android.graphics.Color +import android.graphics.LinearGradient +import android.graphics.Shader import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable -import androidx.annotation.ColorRes -import androidx.core.content.ContextCompat -import com.gh.common.util.DisplayUtils -import com.halo.assistant.HaloApp -import android.graphics.drawable.StateListDrawable -import android.widget.TextView -import androidx.annotation.DrawableRes -import com.gh.common.util.dip2px -import com.j256.ormlite.stmt.query.In -import android.graphics.Shader -import android.graphics.LinearGradient -import android.graphics.drawable.shapes.RectShape import android.graphics.drawable.ShapeDrawable -import android.opengl.ETC1.getHeight -import com.gh.gamecenter.R -import com.lightgame.utils.Utils +import android.graphics.drawable.StateListDrawable +import android.graphics.drawable.shapes.RectShape +import android.widget.TextView +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import com.gh.common.databind.DrawablesBindingAdapter +import com.gh.common.util.DisplayUtils +import com.gh.common.util.dip2px +import com.halo.assistant.HaloApp object DrawableView { @@ -97,6 +94,17 @@ object DrawableView { return drawable } + @JvmStatic + fun getCornerDrawable(color: Int, radiusLT: Float, radiusRT: Float, radiusRB: Float, radiusLB: Float):Drawable{ + val drawable = GradientDrawable() + drawable.shape = GradientDrawable.RECTANGLE + drawable.setColor(color) + val radiusEach = floatArrayOf(radiusLT.dip2px().toFloat(), radiusLT.dip2px().toFloat(), radiusRT.dip2px().toFloat(), radiusRT.dip2px().toFloat(), + radiusRB.dip2px().toFloat(), radiusRB.dip2px().toFloat(), radiusLB.dip2px().toFloat(), radiusLB.dip2px().toFloat()) + drawable.cornerRadii = radiusEach + return drawable + } + @JvmStatic fun getCornerGradientDrawable(startColor: Int, endColor: Int, radius: Float = 999F): Drawable { val drawable = GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, intArrayOf(startColor, endColor)) diff --git a/app/src/main/java/com/gh/common/view/GameTagFlexLinearLayout.kt b/app/src/main/java/com/gh/common/view/GameTagFlexLinearLayout.kt new file mode 100644 index 0000000000..e5d68aca38 --- /dev/null +++ b/app/src/main/java/com/gh/common/view/GameTagFlexLinearLayout.kt @@ -0,0 +1,102 @@ +package com.gh.common.view + +import android.content.Context +import android.graphics.Color +import android.graphics.Paint +import android.graphics.drawable.GradientDrawable +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import com.gh.common.util.DisplayUtils +import com.gh.gamecenter.R +import com.gh.gamecenter.entity.TagStyleEntity +import kotlin.math.ceil + +/** + * 首页游戏标签,最多显示三个,实际显示个数由控件宽度决定 + */ +class GameTagFlexLinearLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) { + private var mTotalCount = 0 + private var mTags = ArrayList() + private var mItemHeight = DisplayUtils.dip2px(13F) + private var mPadding = DisplayUtils.dip2px(4F) + private var mMargin = DisplayUtils.dip2px(4F) + private var mTextSize = 9F + private var mTotalWidth = 0 + private val mMaxCount = 3 + + init { + gravity = Gravity.CENTER_VERTICAL + } + + fun setTags(datas: ArrayList) { + val tags = datas.take(mMaxCount) + mTags.clear() + mTotalCount = tags.size + mTotalWidth = measuredWidth + val paint = Paint() + paint.textSize = DisplayUtils.sp2px(context, mTextSize).toFloat() + + var currentWidth = 0f + tags.forEach { + val strWidth = getTextWidth(paint, it.name) + if (currentWidth + strWidth + mPadding * 2 + mMargin <= mTotalWidth) { + currentWidth += strWidth + mPadding * 2 + mMargin + mTags.add(it) + } + } + if (mTags.isNotEmpty()) { + addTags() + } + } + + private fun getTextWidth(paint: Paint, str: String?): Int { + var width = 0 + if (str != null && str.isNotEmpty()) { + val len = str.length + val widths = FloatArray(len) + paint.getTextWidths(str, widths) + for (i in 0 until len) { + width += ceil(widths[i].toDouble()).toInt() + } + } + return width + } + + private fun addTags() { + removeAllViews() + mTags.forEach { + addView(createView(it)) + } + } + + private fun createView(tag: TagStyleEntity): View { + return TextView(context).apply { + text = tag.name + includeFontPadding = false + textSize = mTextSize + gravity = Gravity.CENTER + setTextColor(Color.parseColor("#666666")) + + val params = LayoutParams(LayoutParams.WRAP_CONTENT, mItemHeight) + params.setMargins(0, 0, mMargin, 0) + setPadding(mPadding, 0, mPadding, 0) + layoutParams = params + + val gradientDrawable = createBackgroundDrawable() + background = gradientDrawable + } + } + + private fun createBackgroundDrawable(): GradientDrawable { + val gradientDrawable = GradientDrawable() + gradientDrawable.setColor(Color.TRANSPARENT) + gradientDrawable.setStroke(DisplayUtils.dip2px(context, 0.5f), Color.parseColor("#CCCCCC")) + gradientDrawable.cornerRadius = DisplayUtils.dip2px(2f).toFloat() + return gradientDrawable + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/MainActivity.java b/app/src/main/java/com/gh/gamecenter/MainActivity.java index c226f042a0..4b28dec762 100644 --- a/app/src/main/java/com/gh/gamecenter/MainActivity.java +++ b/app/src/main/java/com/gh/gamecenter/MainActivity.java @@ -276,8 +276,8 @@ public class MainActivity extends BaseActivity { SPUtils.setBoolean(Constants.SP_TOP_VIDEO_VOICE, true); //恢复视频流非Wifi提醒 SPUtils.setBoolean(Constants.SP_NON_WIFI_TIPS, true); - //重置顶部视频播放进度 - SPUtils.setString(Constants.SP_TOP_VIDEO_SCHEDULE, ""); + //重置首页视频播放进度 + SPUtils.setString(Constants.SP_HOME_VIDEO_PLAY_RECORD, ""); } @Override @@ -383,7 +383,8 @@ public class MainActivity extends BaseActivity { break; case HOST_LAUNCH_SIMULATOR_GAME: String json = getIntent().getStringExtra(EntranceUtils.KEY_GAME); - GameEntity gameEntity = GsonUtils.getGson().fromJson(json, new TypeToken(){}.getType()); + GameEntity gameEntity = GsonUtils.getGson().fromJson(json, new TypeToken() { + }.getType()); DownloadEntity downloadEntity = SimulatorGameManager.findDownloadEntityByUrl(gameEntity.getApk().get(0).getUrl()); if (downloadEntity != null) { File file = new File(downloadEntity.getPath()); @@ -413,6 +414,7 @@ public class MainActivity extends BaseActivity { /** * 应用跳转 + * * @param packageName */ @SuppressLint("CheckResult") diff --git a/app/src/main/java/com/gh/gamecenter/entity/GameDetailEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/GameDetailEntity.kt index a82d97894c..d83cc9e55d 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/GameDetailEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/GameDetailEntity.kt @@ -112,7 +112,7 @@ class GameDetailEntity( @Parcelize data class Video( - @SerializedName("_id") + @SerializedName("_id", alternate = ["video_id"]) var videoId: String = "", @SerializedName("new_video_count", alternate = ["video_count"]) // 选用 JSON 最后一个值作为 videoCount 的值 var videoCount: Int = 0, diff --git a/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt index 322d5517cf..7b3924f180 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt @@ -227,8 +227,10 @@ data class GameEntity( var active: Boolean = true, @SerializedName("package_dialog") val packageDialog: PackageDialogEntity? = null, + @SerializedName("top_video") + val topVideo: SimpleVideoEntity? = null, - // 本地字段,使用镜像信息 + // 本地字段,使用镜像信息 var useMirrorInfo: Boolean = false, // 从启动弹窗跳转到对应游戏列表时候记录的启动弹窗数据 (ugly ugly ugly) var welcomeDialogId: String? = null, @@ -260,7 +262,8 @@ data class GameEntity( @IgnoredOnParcel var name: String? get() = if (shouldUseMirrorInfo()) { - mirrorData?.mName?.removeSuffix(".") + if (shouldShowNameSuffix) mirrorData?.nameSuffix ?: "" else "" + mirrorData?.mName?.removeSuffix(".") + if (shouldShowNameSuffix) mirrorData?.nameSuffix + ?: "" else "" } else { mName?.removeSuffix(".") + if (shouldShowNameSuffix) nameSuffix else "" } diff --git a/app/src/main/java/com/gh/gamecenter/entity/HomeContent.kt b/app/src/main/java/com/gh/gamecenter/entity/HomeContent.kt index b8ace30b94..ae17158597 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/HomeContent.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/HomeContent.kt @@ -13,4 +13,8 @@ data class HomeContent(@SerializedName("link_type") @SerializedName("link_column") val linkColumn: SubjectEntity? = null, @SerializedName("link_top_game_comment") - val linkTopGameComment: List? = null) \ No newline at end of file + val linkTopGameComment: List? = null, + @SerializedName("display_content") + val displayContent: String = "", + @SerializedName("recommend_text") + val recommendText: String = "") \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/entity/SimpleVideoEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/SimpleVideoEntity.kt new file mode 100644 index 0000000000..65f94c7347 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/entity/SimpleVideoEntity.kt @@ -0,0 +1,14 @@ +package com.gh.gamecenter.entity + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.android.parcel.Parcelize + +@Parcelize +class SimpleVideoEntity( + @SerializedName("video_id") + val id: String = "", + val title: String = "", + val poster: String = "", + val url: String = "" +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailFragment.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailFragment.kt index 42154a6062..bfb00732cc 100644 --- a/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailFragment.kt @@ -49,6 +49,7 @@ import com.gh.gamecenter.gamedetail.entity.NewGameDetailEntity import com.gh.gamecenter.gamedetail.fuli.FuLiFragment import com.gh.gamecenter.gamedetail.rating.RatingFragment import com.gh.gamecenter.gamedetail.video.TopVideoView +import com.gh.gamecenter.home.video.ScrollCalculatorHelper import com.gh.gamecenter.mvvm.Status import com.gh.gamecenter.normal.NormalFragment import com.gh.gamecenter.packagehelper.PackageViewModel @@ -57,7 +58,6 @@ import com.gh.gamecenter.tag.TagsActivity import com.gh.gamecenter.user.UserViewModel import com.google.android.material.appbar.AppBarLayout import com.google.android.material.tabs.TabLayout -import com.google.gson.reflect.TypeToken import com.halo.assistant.HaloApp import com.halo.assistant.fragment.WebFragment import com.lightgame.download.DataWatcher @@ -824,10 +824,12 @@ class GameDetailFragment : NormalFragment() { return@setOnClickListener } mOrientationUtils?.resolveByClick() + horizontalVideoView.uuid = mTopVideoView.uuid horizontalVideoView.viewModel = mViewModel horizontalVideoView.video = topVideo horizontalVideoView.updateThumb(topVideo.poster) horizontalVideoView.violenceUpdateMuteStatus() + mTopVideoView.uploadVideoStreamingPlaying("点击全屏") MtaHelper.onEvent("游戏详情_顶部视频", "顶部视频-点击进入全屏", mTopVideoView.combinedTitleAndId) } @@ -1104,22 +1106,11 @@ class GameDetailFragment : NormalFragment() { private fun releaseVideo() { if (mViewModel.displayTopVideo) { - recordVideoSchedule() mTopVideoView.release() mTopVideoView.disposableTimer() } } - //保存视频进度 - private fun recordVideoSchedule() { - val datas = SPUtils.getString(Constants.SP_TOP_VIDEO_SCHEDULE) - val type = object : TypeToken>() {}.type - val schedules = GsonUtils.gson.fromJson>(datas, type) - ?: hashMapOf() - schedules[mTopVideoView.video?.url ?: ""] = mTopVideoView.getCurrentPosition() - SPUtils.setString(Constants.SP_TOP_VIDEO_SCHEDULE, GsonUtils.toJson(schedules)) - } - private fun tabPerformClick(position: Int) { if (!mIsPreferredTabSelected) { mViewPager.currentItem = position @@ -1175,6 +1166,13 @@ class GameDetailFragment : NormalFragment() { override fun onPause() { super.onPause() pauseVideo() + if (mViewModel.displayTopVideo) { + val currentPosition = mTopVideoView.getCurrentPosition() + val topVideo = mNewGameDetailEntity?.topVideo + if (topVideo != null) { + ScrollCalculatorHelper.savePlaySchedule(MD5Utils.getContentMD5(topVideo.url), currentPosition) + } + } DownloadManager.getInstance(context).removeObserver(dataWatcher) } diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/video/TopVideoView.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/video/TopVideoView.kt index 3e55400dca..736e749961 100644 --- a/app/src/main/java/com/gh/gamecenter/gamedetail/video/TopVideoView.kt +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/video/TopVideoView.kt @@ -1,6 +1,7 @@ package com.gh.gamecenter.gamedetail.video import android.content.Context +import android.content.pm.ActivityInfo import android.os.Handler import android.util.AttributeSet import android.view.GestureDetector @@ -8,27 +9,33 @@ import android.view.MotionEvent import android.view.Surface import android.view.View import android.widget.ImageView +import android.widget.SeekBar import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import com.gh.common.constant.Constants import com.gh.common.observer.MuteCallback import com.gh.common.observer.VolumeObserver +import com.gh.common.runOnIoThread +import com.gh.common.runOnUiThread import com.gh.common.util.* +import com.gh.download.cache.ExoCacheManager import com.gh.gamecenter.R import com.gh.gamecenter.entity.GameDetailEntity import com.gh.gamecenter.gamedetail.GameDetailViewModel -import com.google.gson.reflect.TypeToken +import com.gh.gamecenter.home.video.ScrollCalculatorHelper +import com.gh.gamecenter.video.detail.CustomManager import com.lightgame.utils.Utils -import com.shuyu.gsyvideoplayer.GSYVideoManager import com.shuyu.gsyvideoplayer.utils.CommonUtil import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer import com.shuyu.gsyvideoplayer.video.base.GSYVideoView +import com.shuyu.gsyvideoplayer.video.base.GSYVideoViewBridge import com.squareup.picasso.Picasso import io.reactivex.disposables.Disposable import kotlinx.android.synthetic.main.layout_game_detail_video_portrait.view.* import kotlinx.android.synthetic.main.piece_video_control.view.* import kotlinx.android.synthetic.main.piece_video_replay.view.* +import java.util.* class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : StandardGSYVideoPlayer(context, attrs) { @@ -38,7 +45,10 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS var gameName = "" var video: GameDetailEntity.Video? = null var viewModel: GameDetailViewModel? = null + var uuid = UUID.randomUUID().toString() private var mMuteDisposable: Disposable? = null + private var mContentLength = 0.0 + private var mIsAutoPlay = false val combinedTitleAndId: String get() = StringUtils.combineTwoString(video?.title, video?.videoId) @@ -118,6 +128,7 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS } fun startPlayLogic(isAutoPlay: Boolean) { + mIsAutoPlay = isAutoPlay violenceUpdateMuteStatus() if (isAutoPlay) { MtaHelper.onEvent("游戏详情_顶部视频", "视频播放方式", "自动播放") @@ -126,16 +137,7 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS MtaHelper.onEvent("游戏详情_顶部视频", "视频播放方式", "手动播放") } if (isAutoPlay) { - var seekTime = 0L - val datas = SPUtils.getString(Constants.SP_TOP_VIDEO_SCHEDULE) - val type = object : TypeToken>() {}.type - val schedules = GsonUtils.gson.fromJson>(datas, type)?: mapOf() - schedules.forEach { - if (it.key == video?.url) { - seekTime = it.value - return@forEach - } - } + val seekTime = ScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(video?.url)) seekOnStart = seekTime } startPlayLogic() @@ -183,10 +185,11 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS private fun mute(isManual: Boolean = false) { viewModel?.videoIsMuted = true volume.setImageResource(R.drawable.ic_game_detail_volume_off) - GSYVideoManager.instance().isNeedMute = true + CustomManager.getCustomManager(getKey()).isNeedMute = true SPUtils.setBoolean(Constants.SP_TOP_VIDEO_VOICE, true) if (isManual) { Utils.toast(context, "当前处于静音状态") + uploadVideoStreamingPlaying("点击静音") MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击静音", combinedTitleAndId) } } @@ -194,9 +197,12 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS private fun unMute(isManual: Boolean = false) { viewModel?.videoIsMuted = false volume.setImageResource(R.drawable.ic_game_detail_volume_on) - GSYVideoManager.instance().isNeedMute = false + CustomManager.getCustomManager(getKey()).isNeedMute = false SPUtils.setBoolean(Constants.SP_TOP_VIDEO_VOICE, false) - if (isManual) MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-解除静音", combinedTitleAndId) + if (isManual) { + uploadVideoStreamingPlaying("取消静音") + MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-解除静音", combinedTitleAndId) + } } override fun onSeekComplete() { @@ -232,27 +238,33 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS // 不延迟的话 isCacheFile 可能直接返回 false postDelayed({ // 这个库的 NetworkUtils.isWifiConnected 可能会触发空指针,这里换为我们自己的 - if (!com.shuyu.gsyvideoplayer.utils.NetworkUtils.isAvailable(mContext) && !GSYVideoManager.instance().isCacheFile) { + if (!com.shuyu.gsyvideoplayer.utils.NetworkUtils.isAvailable(mContext) && !gsyVideoManager.isCacheFile) { Utils.toast(context, "网络异常,请检查手机网络状态") - } else if (!NetworkUtils.isWifiConnected(mContext) && !GSYVideoManager.instance().isCacheFile) { + } else if (!NetworkUtils.isWifiConnected(mContext) && !gsyVideoManager.isCacheFile) { Utils.toast(context, "当前为非Wi-Fi环境,请注意流量消耗") } }, 100) // } } + override fun getGSYVideoManager(): GSYVideoViewBridge { + CustomManager.getCustomManager(getKey()).initContext(context.applicationContext) + return CustomManager.getCustomManager(getKey()) + } + + private fun getKey(): String { + return uuid + } override fun onAutoCompletion() { // 这个方法在内核被释放时也会被回调,所以可以用来统计播放量和播放时长 val playedTime = (gsyVideoManager.currentPosition / 1000).toInt() -// MtaHelper.onEventWithTime("视频播放量_按游戏", playedTime, gameName, combinedTitleAndId) MtaHelper.onEventWithTime("视频播放量_按位置", playedTime, "游戏详情-顶部视频", combinedTitleAndId) MtaHelper.onEventWithTime("视频播放量_游戏加位置", playedTime, gameName, "游戏详情-顶部视频") //播放完成后判断是否已缓冲完毕,没有完成显示播放错误状态 if (mBufferPoint != 0 && mBufferPoint != 100 && isShown) { -// Utils.toast(context, "网络错误,视频播放失败") gsyVideoManager.releaseMediaPlayer() changeUiToPreparingShow() postDelayed({ @@ -262,9 +274,15 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS } }, 10 * 1000) } + uploadVideoStreamingPlaying("播放完毕") super.onAutoCompletion() } + override fun onStopTrackingTouch(seekBar: SeekBar?) { + super.onStopTrackingTouch(seekBar) + uploadVideoStreamingPlaying("拖动") + } + override fun isShowNetConfirm(): Boolean { return (!mOriginUrl.startsWith("file") && !mOriginUrl.startsWith("android.resource") && !CommonUtil.isWifiConnected(context) && mNeedShowWifiTip) @@ -291,6 +309,7 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击重新播放", combinedTitleAndId) startButton.performClick() violenceUpdateMuteStatus() + uploadVideoStreamingPlaying("重新播放") } updateThumb(video!!.poster) } else { @@ -329,6 +348,7 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS super.onSurfaceUpdated(surface) if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == View.VISIBLE) { mThumbImageViewLayout.visibility = View.INVISIBLE + uploadVideoStreamingPlaying("开始播放") } } @@ -387,6 +407,17 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS errorContainer.visibility = View.VISIBLE } + override fun releaseVideos() { + uploadVideoStreamingPlaying("结束播放") + CustomManager.releaseAllVideos(getKey()) + } + + override fun onVideoPause() { + super.onVideoPause() + uploadVideoStreamingPlaying("暂停播放") + } + + override fun onClick(v: View) { when (v.id) { R.id.start -> { @@ -406,4 +437,38 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS fun getCurrentPosition(): Long { return mCurrentPosition } + + fun uploadVideoStreamingPlaying(action: String) { + if (video == null || video?.url.isNullOrEmpty()) return + runOnIoThread { + val isLandscape = if (mOrientationUtils != null) { + mOrientationUtils.screenType == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + } else { + false + } + val videoPlayModel = if (!isLandscape) { + if (mIsAutoPlay) "自动播放" else "点击播放" + } else "全屏播放" + val videoPlayTs = currentPositionWhenPlaying / 1000 + val videoTotalTime = duration / 1000 + val progress = if (videoTotalTime != 0) videoPlayTs.toFloat() / videoTotalTime.toFloat() * 100 else 0f + if (mContentLength == 0.0) { + mContentLength = ExoCacheManager.getContentLength(video!!.url) / 1024.0 / 1024.0 + } + + //https://exoplayer.dev/hello-world.html#a-note-on-threading + runOnUiThread { + LogUtils.uploadTopVideoStreamingPlaying(action, video?.videoId, video?.title, viewModel?.game?.id, viewModel?.game?.name, + videoPlayModel, videoPlayStatus(), mContentLength, videoTotalTime, videoPlayTs, progress.toInt()) + } + } + } + + private fun videoPlayStatus(): String { + return when (mCurrentState) { + CURRENT_STATE_PLAYING, CURRENT_STATE_PREPAREING, CURRENT_STATE_PLAYING_BUFFERING_START -> "play" + GSYVideoView.CURRENT_STATE_PAUSE -> "pause" + else -> "" + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/home/HomeFragment.kt b/app/src/main/java/com/gh/gamecenter/home/HomeFragment.kt index 5bf843d2fd..ae949e0adf 100644 --- a/app/src/main/java/com/gh/gamecenter/home/HomeFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/home/HomeFragment.kt @@ -8,11 +8,10 @@ import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.gh.base.fragment.BaseFragment +import com.gh.common.AppExecutor import com.gh.common.TimeElapsedHelper import com.gh.common.exposure.ExposureListener -import com.gh.common.util.DialogUtils -import com.gh.common.util.MtaHelper -import com.gh.common.util.observeNonNull +import com.gh.common.util.* import com.gh.common.view.FixLinearLayoutManager import com.gh.common.xapk.XapkInstaller import com.gh.common.xapk.XapkUnzipStatus @@ -27,11 +26,15 @@ import com.gh.gamecenter.eventbus.EBUISwitch import com.gh.gamecenter.fragment.MainWrapperFragment import com.gh.gamecenter.game.gallery.GameGallerySlideViewHolder import com.gh.gamecenter.home.slide.HomeSlideListAdapter +import com.gh.gamecenter.home.video.ScrollCalculatorHelper +import com.gh.gamecenter.video.detail.CustomManager import com.lightgame.download.DataWatcher import com.lightgame.download.DownloadEntity +import com.shuyu.gsyvideoplayer.GSYVideoManager import kotterknife.bindView import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode +import tv.danmaku.ijk.media.exo2.ExoSourceManager class HomeFragment : BaseFragment() { @@ -45,6 +48,8 @@ class HomeFragment : BaseFragment() { private lateinit var mElapsedHelper: TimeElapsedHelper private lateinit var mExposureListener: ExposureListener + private lateinit var mScrollCalculatorHelper: ScrollCalculatorHelper + private lateinit var mAutomaticLayoutManager: LinearLayoutManager private val dataWatcher = object : DataWatcher() { override fun onDataChanged(downloadEntity: DownloadEntity) { @@ -71,9 +76,16 @@ class HomeFragment : BaseFragment() { mListAdapter.setLoadStatus(it) mNoConn.visibility = if (it == LoadStatus.INIT_FAILED) View.VISIBLE else View.GONE mLoading.visibility = if (it == LoadStatus.INIT_LOADING) View.VISIBLE else View.GONE + if (it == LoadStatus.INIT_LOADED) { + AppExecutor.uiExecutor.executeWithDelay(Runnable { + scroll() + mScrollCalculatorHelper.onScrollStateChanged(mBinding.gameList, RecyclerView.SCROLL_STATE_IDLE) + }, 100) + } }) mElapsedHelper = TimeElapsedHelper() + mScrollCalculatorHelper = ScrollCalculatorHelper(R.id.autoVideoView, 0) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -81,7 +93,8 @@ class HomeFragment : BaseFragment() { mBinding = FragmentGameBinding.bind(view) mBinding.gameRefresh.setColorSchemeColors(ContextCompat.getColor(requireContext(), R.color.theme)) mBinding.loadStatus = LoadStatus.LIST_LOADED - mLayoutManager = FixLinearLayoutManager(requireContext()) + mAutomaticLayoutManager = FixLinearLayoutManager(requireContext()) + mLayoutManager = mAutomaticLayoutManager mListAdapter = HomeFragmentAdapter(requireContext(), mViewModel, mLayoutManager) mExposureListener = ExposureListener(this, mListAdapter) (mBinding.gameList.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false @@ -91,9 +104,15 @@ class HomeFragment : BaseFragment() { mBinding.gameList.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) + mScrollCalculatorHelper.onScrollStateChanged(mBinding.gameList, newState) if (mLayoutManager.findLastVisibleItemPosition() == mListAdapter.itemCount - 1 && RecyclerView.SCROLL_STATE_IDLE == newState) mViewModel.getHomeContent(false) } + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + scroll() + } }) mBinding.gameList.addOnScrollListener(mExposureListener) @@ -109,8 +128,16 @@ class HomeFragment : BaseFragment() { } } + private fun scroll() { + val firstVisibleItem = mAutomaticLayoutManager.findFirstVisibleItemPosition() + val lastVisibleItem = mAutomaticLayoutManager.findLastVisibleItemPosition() + mScrollCalculatorHelper.onScroll(mViewModel.itemDataList.value, firstVisibleItem, lastVisibleItem) + } + override fun onPause() { super.onPause() + pauseVideo() + DownloadManager.getInstance(context).removeObserver(dataWatcher) handleAutoScroll(false) @@ -119,6 +146,7 @@ class HomeFragment : BaseFragment() { } override fun onResume() { + resumeVideo() if (isEverPause) mListAdapter.notifyDataSetChanged() super.onResume() DownloadManager.getInstance(context).addObserver(dataWatcher) @@ -128,6 +156,39 @@ class HomeFragment : BaseFragment() { mElapsedHelper.resumeCounting() } + private fun pauseVideo() { + if (mScrollCalculatorHelper.currentPosition >= 0) { + mScrollCalculatorHelper.currentPlayer?.resetDetailMask() + mScrollCalculatorHelper.currentPlayer?.onVideoPause() + val currentPosition = mScrollCalculatorHelper.currentPlayer?.getCurrentPosition() ?: 0L + val topVideo = mViewModel.itemDataList.value?.get(mScrollCalculatorHelper.currentPosition)?.attachGame?.linkGame?.topVideo + if (topVideo != null) { + ScrollCalculatorHelper.savePlaySchedule(MD5Utils.getContentMD5(topVideo.url), currentPosition) + } + } + } + + private fun resumeVideo() { + if (mScrollCalculatorHelper.currentPlayer != null) { + val topVideo = mViewModel.itemDataList.value?.get(mScrollCalculatorHelper.currentPosition)?.attachGame?.linkGame?.topVideo + if (topVideo != null) { + val position = ScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(topVideo.url)) + //这里必须要延迟操作,否则会白屏 + mBaseHandler.postDelayed({ + mScrollCalculatorHelper.currentPlayer?.seekTo(position) + mScrollCalculatorHelper.currentPlayer?.onVideoResume(false) + }, 50) + } + } + } + + override fun onDestroy() { + super.onDestroy() + if (mScrollCalculatorHelper.currentPlayer != null) { + mScrollCalculatorHelper.currentPlayer?.release() + } + } + private fun handleAutoScroll(resumeScroll: Boolean) { val view = mBinding.gameList.layoutManager?.findViewByPosition(0) val recyclerView = view?.findViewById(R.id.recycler_view) diff --git a/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt b/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt index 53b4bea005..4831b887a8 100644 --- a/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt @@ -1,8 +1,11 @@ package com.gh.gamecenter.home import android.content.Context +import android.graphics.Color +import android.graphics.drawable.ColorDrawable import android.view.View import android.view.ViewGroup +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.gh.common.constant.ItemViewType @@ -30,6 +33,8 @@ import com.gh.gamecenter.home.slide.HomeSlideListViewHolder import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel import com.halo.assistant.fragment.game.GamePluginAdapter import com.lightgame.download.DownloadEntity +import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder +import com.shuyu.gsyvideoplayer.video.base.GSYVideoView import java.util.* class HomeFragmentAdapter(context: Context, @@ -185,7 +190,43 @@ class HomeFragmentAdapter(context: Context, eTrace = null, event = ExposureType.EXPOSURE) - holder.binding.videoPlay.goneIf(homeItemData.attachGame?.linkType != "video") + holder.binding.gameImage.goneIf(homeItemData.attachGame?.displayContent == "video" && homeItemData.attachGame?.linkGame?.topVideo != null) + holder.binding.gameBrief.goneIf(homeItemData.attachGame?.displayContent == "video" && homeItemData.attachGame?.linkGame?.topVideo != null) + holder.binding.autoVideoView.goneIf(homeItemData.attachGame?.displayContent != "video" || homeItemData.attachGame?.linkGame?.topVideo == null) + if (!holder.binding.autoVideoView.isInPlayingState + && homeItemData.attachGame?.displayContent == "video" + && homeItemData.attachGame?.linkGame?.topVideo != null) { + val topVideo = homeItemData.attachGame?.linkGame?.topVideo + val homeSetting = homeItemData.attachGame?.linkGame?.homeSetting + GSYVideoOptionBuilder() + .setIsTouchWiget(false) + .setUrl(topVideo?.url ?: "") + .setRotateViewAuto(false) + .setCacheWithPlay(true) + .setRotateWithSystem(false) + .setReleaseWhenLossAudio(true) + .setLooping(false) + .setShowFullAnimation(false) + .build(holder.binding.autoVideoView) + holder.binding.autoVideoView.updateThumb(topVideo?.poster ?: "") + holder.binding.autoVideoView.setGameEntity(homeItemData.attachGame?.linkGame) + if (!homeSetting?.placeholderColor.isNullOrEmpty()) { + try { + holder.binding.autoVideoView.detailBtn.background = ColorDrawable(Color.parseColor(game.homeSetting.placeholderColor)) + } catch (e: Exception) { + val color = RandomUtils.getRandomPlaceholderColor() + holder.binding.autoVideoView.detailBtn.setBackgroundColor(ContextCompat.getColor(holder.itemView.context, color)) + } + } else { + val color = RandomUtils.getRandomPlaceholderColor() + holder.binding.autoVideoView.detailBtn.setBackgroundColor(ContextCompat.getColor(holder.itemView.context, color)) + } + holder.binding.autoVideoView.detailBtn.setOnClickListener { + holder.itemView.performClick() + holder.binding.autoVideoView.uploadVideoStreamingPlaying("点击遮罩") + } + } + holder.binding.gameImage.setOnClickListener { if (homeItemData.attachGame?.linkType == "video") { MtaHelper.onEvent("首页_新", "点击", "内容" + homeItemData.blockPosition + "_" + game.name + "_视频详情") @@ -202,6 +243,13 @@ class HomeFragmentAdapter(context: Context, holder.itemView.setOnClickListener { MtaHelper.onEvent("首页_新", "点击", "内容" + homeItemData.blockPosition + "_" + game.name) GameDetailActivity.startGameDetailActivity(mContext, game.id, "(首页-游戏[" + game.name + "])", homeItemData.exposureEvent) + if (holder.binding.autoVideoView.isInPlayingState) { + holder.binding.autoVideoView.uploadVideoStreamingPlaying("游戏详情-播放点击") + } else { + if (holder.binding.autoVideoView.currentState == GSYVideoView.CURRENT_STATE_AUTO_COMPLETE) { + holder.binding.autoVideoView.uploadVideoStreamingPlaying("游戏详情-完播点击") + } + } } } diff --git a/app/src/main/java/com/gh/gamecenter/home/HomeGameItemViewHolder.kt b/app/src/main/java/com/gh/gamecenter/home/HomeGameItemViewHolder.kt index c182638fc3..4470fdb00a 100644 --- a/app/src/main/java/com/gh/gamecenter/home/HomeGameItemViewHolder.kt +++ b/app/src/main/java/com/gh/gamecenter/home/HomeGameItemViewHolder.kt @@ -4,6 +4,7 @@ import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.view.View import com.gh.base.BaseRecyclerViewHolder +import com.gh.common.AppExecutor import com.gh.common.util.RandomUtils import com.gh.gamecenter.databinding.HomeGameItemBinding import com.gh.gamecenter.entity.GameEntity @@ -13,10 +14,13 @@ class HomeGameItemViewHolder(val binding: HomeGameItemBinding) : BaseRecyclerVie fun bindGame(game: GameEntity) { binding.data = game binding.gameBrief.text = game.brief - binding.gameTags.visibility = if (game.tagStyle.isNotEmpty()) { - binding.gameTags.text = game.tagStyle[0].name - View.VISIBLE - } else View.GONE + + binding.gameTags.postDelayed({ + binding.gameTags.visibility = if (game.tagStyle.isNotEmpty()) { + binding.gameTags.setTags(game.tagStyle) + View.VISIBLE + } else View.GONE + }, 5) val hierarchy = binding.gameImage.hierarchy try { diff --git a/app/src/main/java/com/gh/gamecenter/home/video/AutomaticVideoView.kt b/app/src/main/java/com/gh/gamecenter/home/video/AutomaticVideoView.kt new file mode 100644 index 0000000000..4e184d3d09 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/home/video/AutomaticVideoView.kt @@ -0,0 +1,197 @@ +package com.gh.gamecenter.home.video + +import android.content.Context +import android.util.AttributeSet +import android.view.Surface +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import com.gh.common.constant.Constants +import com.gh.common.runOnIoThread +import com.gh.common.runOnUiThread +import com.gh.common.util.* +import com.gh.download.cache.ExoCacheManager +import com.gh.gamecenter.R +import com.gh.gamecenter.entity.GameEntity +import com.gh.gamecenter.video.detail.CustomManager +import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer +import com.shuyu.gsyvideoplayer.video.base.GSYVideoViewBridge +import com.squareup.picasso.Picasso +import io.reactivex.disposables.Disposable +import moe.codeest.enviews.ENDownloadView +import java.util.* + +class AutomaticVideoView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : StandardGSYVideoPlayer(context, attrs) { + var thumbImage: ImageView = findViewById(R.id.thumbImage) + var detailBtn: TextView = findViewById(R.id.detailBtn) + private var mMuteDisposable: Disposable? = null + private var mMaskDisposable: Disposable? = null + private val mUUID = UUID.randomUUID().toString() + private var shouldShowMask = true + private var mContentLength = 0.0 + private var gameEntity: GameEntity? = null + + override fun getLayoutId(): Int { + return R.layout.empty_control_video + } + + fun setGameEntity(entity: GameEntity?) { + gameEntity = entity + } + + fun startPlayLogic(showMask: Boolean) { + shouldShowMask = showMask + startPlayLogic() + } + + override fun startPlayLogic() { + super.startPlayLogic() + val topVideoVoiceStatus = SPUtils.getBoolean(Constants.SP_TOP_VIDEO_VOICE, true) + if (topVideoVoiceStatus) { + violenceUpdateMuteStatus() + } + resetDetailMask() + } + + private fun violenceUpdateMuteStatus() { + if (mMuteDisposable != null) { + mMuteDisposable?.dispose() + mMuteDisposable = null + } + mMuteDisposable = rxTimer(25) { + if (it >= 400) { + mMuteDisposable?.dispose() + mMuteDisposable = null + } + CustomManager.getCustomManager(getKey()).isNeedMute = true + } + } + + private fun disposableTimer() { + if (mMuteDisposable != null && !mMuteDisposable!!.isDisposed) { + mMuteDisposable?.dispose() + mMuteDisposable = null + } + } + + private fun showDetailMask() { + if (mMaskDisposable != null) { + mMaskDisposable?.dispose() + mMaskDisposable = null + } + mMaskDisposable = countDownTimer(5) { finish, _ -> + if (finish) { + val offset = 44f.dip2px().toFloat() + detailBtn.translationY = offset + detailBtn.alpha = 0f + detailBtn.visibility = View.VISIBLE + detailBtn.animate().translationY(0f).alpha(1f).setDuration(800).doOnEnd { + uploadVideoStreamingPlaying("出现遮罩") + }.start() + } + } + } + + fun resetDetailMask() { + detailBtn.visibility = View.GONE + if (mMaskDisposable != null && !mMaskDisposable!!.isDisposed) { + mMaskDisposable?.dispose() + mMaskDisposable = null + } + } + + override fun releaseVideos() { + super.releaseVideos() + uploadVideoStreamingPlaying("结束播放") + CustomManager.releaseAllVideos(getKey()) + } + + override fun onAutoCompletion() { + super.onAutoCompletion() + uploadVideoStreamingPlaying("播放完毕") + } + + override fun getGSYVideoManager(): GSYVideoViewBridge { + CustomManager.getCustomManager(getKey()).initContext(context.applicationContext) + return CustomManager.getCustomManager(getKey()) + } + + fun getKey(): String { + return mUUID + } + + /******************* 下方两个重载方法,在播放开始前不屏蔽封面,不需要可屏蔽 ********************/ + + override fun onSurfaceUpdated(surface: Surface) { + super.onSurfaceUpdated(surface) + if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == View.VISIBLE) { + mThumbImageViewLayout.visibility = View.INVISIBLE + if (shouldShowMask) { + showDetailMask() + shouldShowMask = true + } + uploadVideoStreamingPlaying("开始播放") + } + } + + fun updateThumb(url: String) { + Picasso.with(context).load(url).fit().into(thumbImage) + } + + override fun setViewShowState(view: View?, visibility: Int) { + if (view === mThumbImageViewLayout && visibility != View.VISIBLE) { + return + } + super.setViewShowState(view, visibility) + } + + /********************************各类UI的状态显示*********************************************/ + + override fun touchSurfaceMoveFullLogic(absDeltaX: Float, absDeltaY: Float) { + super.touchSurfaceMoveFullLogic(absDeltaX, absDeltaY) + //不给触摸快进,如果需要,屏蔽下方代码即可 + mChangePosition = false + //不给触摸音量,如果需要,屏蔽下方代码即可 + mChangeVolume = false + //不给触摸亮度,如果需要,屏蔽下方代码即可 + mBrightness = false + } + + override fun touchDoubleUp() { + + } + + fun getCurrentPosition(): Long { + return mCurrentPosition + } + + fun setCurrentPosition(mCurrentPosition: Long) { + this.mCurrentPosition = mCurrentPosition + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + disposableTimer() + resetDetailMask() + } + + fun uploadVideoStreamingPlaying(action: String) { + if (gameEntity == null) return + val topVideo = gameEntity?.topVideo + if (topVideo == null || topVideo.url.isEmpty()) return + runOnIoThread { + val videoPlayTs = currentPositionWhenPlaying / 1000 + val videoTotalTime = duration / 1000 + val progress = if (videoTotalTime != 0) videoPlayTs.toFloat() / videoTotalTime.toFloat() * 100 else 0f + if (mContentLength == 0.0) { + mContentLength = ExoCacheManager.getContentLength(topVideo.url) / 1024.0 / 1024.0 + } + + //https://exoplayer.dev/hello-world.html#a-note-on-threading + runOnUiThread { + LogUtils.uploadHomeVideoStreamingPlaying(action, detailBtn.visibility == View.VISIBLE, topVideo.id, topVideo.title, + gameEntity?.id, gameEntity?.name, mContentLength, videoTotalTime, videoPlayTs, progress.toInt()) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/home/video/ScrollCalculatorHelper.kt b/app/src/main/java/com/gh/gamecenter/home/video/ScrollCalculatorHelper.kt new file mode 100644 index 0000000000..9458716ac1 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/home/video/ScrollCalculatorHelper.kt @@ -0,0 +1,150 @@ +package com.gh.gamecenter.home.video + +import android.content.Context +import android.graphics.Rect +import android.os.Handler +import android.os.Looper +import android.preference.PreferenceManager +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.constant.Constants +import com.gh.common.util.MD5Utils +import com.gh.common.util.NetworkUtils +import com.gh.common.util.SPUtils +import com.gh.gamecenter.entity.SimpleVideoEntity +import com.gh.gamecenter.home.HomeItemData +import com.gh.gamecenter.video.detail.CustomManager +import com.halo.assistant.HaloApp +import com.halo.assistant.fragment.SettingsFragment +import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer + +class ScrollCalculatorHelper(private val playId: Int, private val rangeTop: Int) { + private var firstVisible = -1 + private var lastVisible = 0 + private var visibleCount = 0 + private var runnable: PlayRunnable? = null + var mHomeItemData: List? = null + var currentPlayer: AutomaticVideoView? = null + var currentPosition = -1 + var mListRv: RecyclerView? = null + + private val playHandler = Handler(Looper.getMainLooper()) + + fun onScrollStateChanged(view: RecyclerView?, scrollState: Int) { + mListRv = view + if (scrollState == RecyclerView.SCROLL_STATE_IDLE) { + playVideo(view) + } + } + + fun onScroll(homeItemData: List?, firstVisibleItem: Int, lastVisibleItem: Int) { + mHomeItemData = homeItemData + firstVisible = firstVisibleItem + lastVisible = lastVisibleItem + visibleCount = lastVisibleItem - firstVisibleItem + releaseIfNeeded() + } + + //判断player是否划出了屏幕,划出了屏幕则需要释放 + private fun releaseIfNeeded() { + val rect = Rect() + if (currentPlayer != null) { + currentPlayer?.getLocalVisibleRect(rect) + val height = currentPlayer?.height + if (rect.top != 0 || rect.bottom != height) { + CustomManager.releaseAllVideos(currentPlayer?.getKey()) + currentPlayer?.resetDetailMask() + currentPlayer = null + currentPosition = -1 + } + } + } + + private fun playVideo(view: RecyclerView?) { + if (view == null) return + val layoutManager = view.layoutManager + var gsyBaseVideoPlayer: AutomaticVideoView + for (i in firstVisible until lastVisible + 1) { + if (layoutManager == null || mHomeItemData.isNullOrEmpty()) return + + val child = mListRv?.findViewHolderForAdapterPosition(i)?.itemView + val player: View? = child?.findViewById(playId) + if (player == null || player !is AutomaticVideoView) continue + + val attachGame = mHomeItemData!![i].attachGame + if (attachGame == null || attachGame.displayContent != "video" + || attachGame.linkGame == null || attachGame.linkGame.topVideo == null) continue + + val rect = Rect() + player.getLocalVisibleRect(rect) + val height = player.height + if (rect.top == 0 && rect.bottom == height) { + gsyBaseVideoPlayer = player + if (runnable != null) { + playHandler.removeCallbacks(runnable!!) + runnable = null + } + if (currentPlayer == gsyBaseVideoPlayer) return + val screenPosition = IntArray(2) + gsyBaseVideoPlayer.getLocationInWindow(screenPosition) + val rangePosition = screenPosition[1] + if (rangePosition >= rangeTop) { + runnable = PlayRunnable(gsyBaseVideoPlayer, attachGame.linkGame.topVideo) + if (currentPlayer != null) { + CustomManager.releaseAllVideos(currentPlayer?.getKey()) + currentPlayer?.resetDetailMask() + } + currentPlayer = gsyBaseVideoPlayer + currentPosition = i + //降低频率 + playHandler.postDelayed(runnable!!, 400) + break + } + } + + } + } + + private inner class PlayRunnable(var gsyBaseVideoPlayer: GSYBaseVideoPlayer?, val topVideo: SimpleVideoEntity) : Runnable { + override fun run() { + if (gsyBaseVideoPlayer != null && !gsyBaseVideoPlayer!!.isInPlayingState) { + + val sp = HaloApp.getInstance().application.getSharedPreferences("${HaloApp.getInstance().application.packageName}_preferences", Context.MODE_PRIVATE) + val isNonWifiPlay = sp.getBoolean(SettingsFragment.TRAFFIC_HOME_VIDEO_SP_KEY, false) + if (!isNonWifiPlay) { + val isWifiConnected = NetworkUtils.isWifiConnected(HaloApp.getInstance().application) + if (isWifiConnected) { + startPlayLogic(gsyBaseVideoPlayer, topVideo) + } + } else { + startPlayLogic(gsyBaseVideoPlayer, topVideo) + } + } + } + } + + private fun startPlayLogic(gsyBaseVideoPlayer: GSYBaseVideoPlayer?, topVideo: SimpleVideoEntity) { + + gsyBaseVideoPlayer?.startPlayLogic() + + val position = getPlaySchedule(MD5Utils.getContentMD5(topVideo.url)) + if (position > 0) { + gsyBaseVideoPlayer?.seekTo(position) + } + } + + companion object { + + fun savePlaySchedule(key: String, schedule: Long) { + val record = SPUtils.getMap(Constants.SP_HOME_VIDEO_PLAY_RECORD) + record[key] = schedule.toString() + SPUtils.setMap(Constants.SP_HOME_VIDEO_PLAY_RECORD, record) + } + + fun getPlaySchedule(key: String): Long { + val record = SPUtils.getMap(Constants.SP_HOME_VIDEO_PLAY_RECORD) + return record[key]?.toLong() ?: 0L + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/video/detail/CustomManager.java b/app/src/main/java/com/gh/gamecenter/video/detail/CustomManager.java index 45ee4348bb..fa2445c203 100644 --- a/app/src/main/java/com/gh/gamecenter/video/detail/CustomManager.java +++ b/app/src/main/java/com/gh/gamecenter/video/detail/CustomManager.java @@ -11,8 +11,10 @@ import com.google.android.exoplayer2.DefaultLoadControl; import com.shuyu.gsyvideoplayer.GSYVideoBaseManager; import com.shuyu.gsyvideoplayer.player.IPlayerManager; import com.shuyu.gsyvideoplayer.utils.CommonUtil; + import java.util.HashMap; import java.util.Map; + import tv.danmaku.ijk.media.exo2.Exo2PlayerManager; import tv.danmaku.ijk.media.exo2.IjkExo2MediaPlayer; @@ -56,6 +58,11 @@ public class CustomManager extends GSYVideoBaseManager { getCustomManager(key).listener().onCompletion(); } getCustomManager(key).releaseMediaPlayer(); + sMap.remove(key); + } + + public static int getVideoManagerSize() { + return sMap.size(); } diff --git a/app/src/main/java/com/gh/gamecenter/video/detail/DetailPlayerView.kt b/app/src/main/java/com/gh/gamecenter/video/detail/DetailPlayerView.kt index 090b350fba..2932d86c3e 100644 --- a/app/src/main/java/com/gh/gamecenter/video/detail/DetailPlayerView.kt +++ b/app/src/main/java/com/gh/gamecenter/video/detail/DetailPlayerView.kt @@ -947,9 +947,9 @@ class DetailPlayerView @JvmOverloads constructor(context: Context, attrs: Attrib fun uploadVideoStreamingPlaying(action: String, msg: String? = "") { runOnIoThread { - val progress = currentPositionWhenPlaying / 1000 + val videoPlayTs = currentPositionWhenPlaying / 1000 val videoTotalTime = duration / 1000 - if (progress == 0 || videoTotalTime == 0) return@runOnIoThread + val progress = if (videoTotalTime != 0) videoPlayTs.toFloat() / videoTotalTime.toFloat() * 100 else 0f if (mContentLength == 0.0) { mContentLength = ExoCacheManager.getContentLength(mVideoEntity!!.url) / 1024.0 / 1024.0 } @@ -957,7 +957,7 @@ class DetailPlayerView @JvmOverloads constructor(context: Context, attrs: Attrib runOnUiThread { LogUtils.uploadVideoStreamingPlaying(action, msg, mViewModel!!.path, mViewModel!!.entranceDetail, mVideoEntity!!.id, mViewModel!!.uuid, mContentLength, - videoTotalTime, progress, videoPlayStatus()) + videoTotalTime, progress.toInt(), videoPlayStatus()) } } } diff --git a/app/src/main/java/com/halo/assistant/fragment/SettingsFragment.java b/app/src/main/java/com/halo/assistant/fragment/SettingsFragment.java index b8022cfe46..ffee1aade3 100644 --- a/app/src/main/java/com/halo/assistant/fragment/SettingsFragment.java +++ b/app/src/main/java/com/halo/assistant/fragment/SettingsFragment.java @@ -87,6 +87,8 @@ public class SettingsFragment extends NormalFragment { SwitchButton mSettingTrafficVideoSb; @BindView(R.id.setting_sb_traffic) SwitchButton mSettingTrafficSb; + @BindView(R.id.setting_sb_home_video) + SwitchButton mSettingHomeVideoSb; @BindView(R.id.setting_tv_cache) TextView mSettingCacheTv; @BindView(R.id.setting_logout_tv) @@ -105,6 +107,7 @@ public class SettingsFragment extends NormalFragment { public static final String AUTO_INSTALL_SP_KEY = "autoinstall"; public static final String CONCERN_GAME_SP_KEY = "concerngame"; public static final String TRAFFIC_VIDEO_SP_KEY = "tranfficvideo"; + public static final String TRAFFIC_HOME_VIDEO_SP_KEY = "tranfficHomevideo"; public static final String FONT_SIZE_SP_KEY = "fontsize"; public static final int INSERT_MOBILE_CODE = 411; @@ -137,7 +140,8 @@ public class SettingsFragment extends NormalFragment { super.onActivityResult(requestCode, resultCode, data); if (requestCode == UsageStatsHelper.USAGE_STATUS_REQUEST_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { mSettingUsageStatsSb.setChecked(UsageStatsHelper.checkForPermission()); - if (UsageStatsHelper.checkForPermission()) EnergyTaskHelper.postEnergyTask("open_game_time"); + if (UsageStatsHelper.checkForPermission()) + EnergyTaskHelper.postEnergyTask("open_game_time"); } // 当前登录方式为手机号登录,更换手机号后,回到该界面更新"退出账号"的手机号 @@ -182,6 +186,7 @@ public class SettingsFragment extends NormalFragment { mSettingAutoinstallSb.setCheckedImmediately(sp.getBoolean(AUTO_INSTALL_SP_KEY, true)); mSettingConcerngameSb.setCheckedImmediately(sp.getBoolean(CONCERN_GAME_SP_KEY, true)); mSettingTrafficVideoSb.setCheckedImmediately(sp.getBoolean(TRAFFIC_VIDEO_SP_KEY, false)); + mSettingHomeVideoSb.setCheckedImmediately(sp.getBoolean(TRAFFIC_HOME_VIDEO_SP_KEY, false)); mSettingTrafficSb.setCheckedImmediately(sp.getBoolean(getTrafficDownloadHintKey(), true)); mSettingTrafficSb.setOnCheckedChangeListener((buttonView, isChecked) -> { MtaHelper.onEvent("我的光环_设置", "流量下载提醒", isChecked ? "打开" : "关闭"); @@ -297,6 +302,7 @@ public class SettingsFragment extends NormalFragment { // mEditor.putBoolean("autodelete", mSettingAutodeleteSb.isChecked()); mEditor.putBoolean(CONCERN_GAME_SP_KEY, mSettingConcerngameSb.isChecked()); mEditor.putBoolean(TRAFFIC_VIDEO_SP_KEY, mSettingTrafficVideoSb.isChecked()); + mEditor.putBoolean(TRAFFIC_HOME_VIDEO_SP_KEY, mSettingHomeVideoSb.isChecked()); mEditor.putBoolean(getTrafficDownloadHintKey(), mSettingTrafficSb.isChecked()); mEditor.putInt(FONT_SIZE_SP_KEY, checkSizeIndex); mEditor.apply(); @@ -350,7 +356,7 @@ public class SettingsFragment extends NormalFragment { R.id.setting_privacy_policy, R.id.setting_user_protocol, R.id.setting_game_submission, R.id.setting_usage_stats, R.id.setting_clean_package, R.id.setting_rl_traffic, - R.id.setting_notification_authority + R.id.setting_notification_authority, R.id.setting_rl_home_video }) @Override public void onClick(final View v) { @@ -492,6 +498,9 @@ public class SettingsFragment extends NormalFragment { PermissionHelper.INSTANCE.toPermissionSetting(requireActivity()); } break; + case R.id.setting_rl_home_video: + mSettingHomeVideoSb.performClick(); + break; default: break; } diff --git a/app/src/main/res/drawable-xxhdpi/ic_home_video_arrow_right.png b/app/src/main/res/drawable-xxhdpi/ic_home_video_arrow_right.png new file mode 100644 index 0000000000..bac4390a57 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_home_video_arrow_right.png differ diff --git a/app/src/main/res/layout/empty_control_video.xml b/app/src/main/res/layout/empty_control_video.xml new file mode 100644 index 0000000000..12b4c59ade --- /dev/null +++ b/app/src/main/res/layout/empty_control_video.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_setting.xml b/app/src/main/res/layout/fragment_setting.xml index 45aa0dc6f3..35ee6ccfcd 100644 --- a/app/src/main/res/layout/fragment_setting.xml +++ b/app/src/main/res/layout/fragment_setting.xml @@ -221,6 +221,32 @@ android:layout_centerVertical="true" /> + + + + + + + @@ -51,58 +51,29 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="4dp" + android:layout_marginTop="7dp" android:layout_marginRight="12dp" android:includeFontPadding="false" android:singleLine="true" android:text="@{data.name}" android:textColor="@color/text_333333" - android:textSize="14sp" + android:textSize="16sp" android:textStyle="bold" - app:layout_constraintBottom_toTopOf="@id/game_brief" app:layout_constraintLeft_toRightOf="@id/game_icon_container" app:layout_constraintRight_toLeftOf="@id/game_rating" app:layout_constraintTop_toTopOf="@id/game_icon_container" app:layout_constraintVertical_chainStyle="packed" tools:text="轩辕剑龙舞云山" /> - - - + android:layout_marginRight="32dp" + app:layout_constraintStart_toStartOf="@id/game_name" + app:layout_constraintEnd_toStartOf="@+id/game_rating" + app:layout_constraintTop_toBottomOf="@+id/game_name" /> + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 590fe3cc92..0056908172 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -134,6 +134,7 @@ 安装完成自动关注游戏 非Wi-Fi环境播放视频提醒 非Wi-Fi环境下载游戏提醒 + 非Wi-Fi环境播放首页视频 统计游戏时长 开启后可以帮你记录玩过每款游戏的时长 通知权限 diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000000..c7ece1b695 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'groovy' + +dependencies { + implementation gradleApi() + implementation localGroovy() + implementation "com.android.tools.build:gradle:4.1.0" + implementation "commons-io:commons-io:2.4" + implementation "org.javassist:javassist:3.25.0-GA" +} +repositories { + google() + jcenter() +} + diff --git a/buildSrc/src/main/groovy/com/gh/gamecenter/plugin/GhPlugin.groovy b/buildSrc/src/main/groovy/com/gh/gamecenter/plugin/GhPlugin.groovy new file mode 100644 index 0000000000..e1e7ac80b0 --- /dev/null +++ b/buildSrc/src/main/groovy/com/gh/gamecenter/plugin/GhPlugin.groovy @@ -0,0 +1,15 @@ +package com.gh.gamecenter.plugin + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class GhPlugin implements Plugin { + + void apply(Project project) { + def log = project.logger + log.error "========================" + log.error "Javassist修改Class!" + log.error "========================" + project.android.registerTransform(new GhTransform(project)) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/com/gh/gamecenter/plugin/GhTransform.groovy b/buildSrc/src/main/groovy/com/gh/gamecenter/plugin/GhTransform.groovy new file mode 100644 index 0000000000..57329ceb8c --- /dev/null +++ b/buildSrc/src/main/groovy/com/gh/gamecenter/plugin/GhTransform.groovy @@ -0,0 +1,196 @@ +package com.gh.gamecenter.plugin + +import com.android.build.api.transform.* +import com.android.build.gradle.internal.pipeline.TransformManager +import javassist.ClassPool +import javassist.CtClass +import javassist.bytecode.ClassFile +import org.apache.commons.codec.digest.DigestUtils +import org.apache.commons.io.FileUtils +import org.apache.commons.io.IOUtils +import org.gradle.api.Project + +import java.util.jar.JarEntry +import java.util.jar.JarFile +import java.util.jar.JarOutputStream + +class GhTransform extends Transform { + + Project project + private ClassPool classPool = ClassPool.getDefault() + + GhTransform(Project project) { + this.project = project + } + + @Override + String getName() { + return "GhTransform" + } + /** +  * 需要处理的数据类型,目前 ContentType有六种枚举类型,通常我们使用比较频繁的有前两种: +  * 1、CONTENT_CLASS:表示需要处理 java 的 class 文件。 +  * 2、CONTENT_JARS:表示需要处理 java 的 class 与 资源文件。 +  * 3、CONTENT_RESOURCES:表示需要处理 java 的资源文件。 +  * 4、CONTENT_NATIVE_LIBS:表示需要处理 native 库的代码。 +  * 5、CONTENT_DEX:表示需要处理 DEX 文件。 +  * 6、CONTENT_DEX_WITH_RESOURCES:表示需要处理 DEX 与 java 的资源文件。  + */ + @Override + Set getInputTypes() { + return TransformManager.CONTENT_CLASS + } + + /** +  * Transform 要操作的内容范围,目前 Scope 有五种基本类型: +  * 1、PROJECT:只有项目内容 +  * 2、SUB_PROJECTS:只有子项目 +  * 3、EXTERNAL_LIBRARIES:只有外部库 +  * 4、TESTED_CODE:由当前变体(包括依赖项)所测试的代码 +  * 5、PROVIDED_ONLY:只提供本地或远程依赖项 +  * SCOPE_FULL_PROJECT 是一个Scope集合,包含Scope.PROJECT,Scope.SUB_PROJECTS,Scope.EXTERNAL_LIBRARIES 这三项,即当前Transform的作用域包括当前项目、子项目以及外部的依赖库 + */ + @Override + Set getScopes() { + //通常我们使用 SCOPE_FULL_PROJECT + return TransformManager.SCOPE_FULL_PROJECT + } + + /** + * 是否需要增量编译 + */ + @Override + boolean isIncremental() { + return false + } + + @Override + void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { + super.transform(transformInvocation) + //添加加android.jar目录 + classPool.appendClassPath(project.android.bootClasspath[0].toString()) + def outputProvider = transformInvocation.outputProvider + // 删除之前的输出 + if (outputProvider != null) { + outputProvider.deleteAll() + } + transformInvocation.inputs.each { input -> + input.directoryInputs.each { dirInput -> + handleDirectory(dirInput.file) + + def dest = outputProvider.getContentLocation(dirInput.name, dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY) + // 将input的目录复制到output指定目录 + FileUtils.copyDirectory(dirInput.file, dest) + } + + } + transformInvocation.inputs.each { input -> + input.jarInputs.each { jarInput -> + if (jarInput.file.exists()) { + def srcFile = handleJar(jarInput.file) + + //必须给jar重新命名,否则会冲突 + def jarName = jarInput.name + def md5 = DigestUtils.md5Hex(jarInput.file.absolutePath) + if (jarName.endsWith(".jar")) { + jarName = jarName.substring(0, jarName.length() - 4) + } + def dest = outputProvider.getContentLocation(md5 + jarName, jarInput.contentTypes, jarInput.scopes, Format.JAR) + FileUtils.copyFile(srcFile, dest) + } + } + } + + } + + void handleDirectory(File dir) { + //将类路径添加到classPool中 + classPool.insertClassPath(dir.absolutePath) + if (dir.isDirectory()) { + dir.eachFileRecurse { file -> + def filePath = file.absolutePath + classPool.insertClassPath(filePath) + if (shouldModify(filePath)) { + def inputStream = new FileInputStream(file) + CtClass ctClass = modifyClass(inputStream) + ctClass.writeFile() + //调用detach方法释放内存 + ctClass.detach() + } + } + } + } + + /** + * 主要步骤: + * 1.遍历所有jar文件 + * 2.解压jar然后遍历所有的class + * 3.读取class的输入流并使用javassit修改,然后保存到新的jar文件中 + */ + File handleJar(File jarFile) { + classPool.appendClassPath(jarFile.absolutePath) + def inputJarFile = new JarFile(jarFile) + def entries = inputJarFile.entries() + //创建一个新的文件 + def outputJarFile = new File(jarFile.parentFile, "temp_" + jarFile.name) + if (outputJarFile.exists()) outputJarFile.delete() + def jarOutputStream = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(outputJarFile))) + while (entries.hasMoreElements()) { + def jarInputEntry = entries.nextElement() + def jarInputEntryName = jarInputEntry.name + + def outputJarEntry = new JarEntry(jarInputEntryName) + jarOutputStream.putNextEntry(outputJarEntry) + + def inputStream = inputJarFile.getInputStream(jarInputEntry) + if (!shouldModify(jarInputEntryName)) { + jarOutputStream.write(IOUtils.toByteArray(inputStream)) + inputStream.close() + continue + } + + def ctClass = modifyClass(inputStream) + def byteCode = ctClass.toBytecode() + ctClass.detach() + inputStream.close() + jarOutputStream.write(byteCode) + jarOutputStream.flush() + } + inputJarFile.close() + jarOutputStream.closeEntry() + jarOutputStream.flush() + jarOutputStream.close() + return outputJarFile + } + + static boolean shouldModify(String filePath) { + return filePath.endsWith(".class") && + !filePath.contains("R.class") && + !filePath.contains('$') && + !filePath.contains('R$') && + !filePath.contains("BuildConfig.class") && + filePath.contains("ExoSourceManager") + } + + CtClass modifyClass(InputStream is) { + def classFile = new ClassFile(new DataInputStream(new BufferedInputStream(is))) + def ctClass = classPool.get(classFile.name) + //判断是否需要解冻 + if (ctClass.isFrozen()) { + ctClass.defrost() + } + + def method = ctClass.getDeclaredMethod("release") + //必须使用全类名,否则编译会找不到类 + def body = ''' + int size = com.gh.gamecenter.video.detail.CustomManager.getVideoManagerSize(); + if (size > 1) { + android.util.Log.e(\"gh_tag\",\"拦截成功\"); + return; + } + ''' + method.insertBefore(body) + return ctClass + + } +} \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/com.gh.gamecenter.plugin.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/com.gh.gamecenter.plugin.properties new file mode 100644 index 0000000000..a9a365e916 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/com.gh.gamecenter.plugin.properties @@ -0,0 +1 @@ +implementation-class=com.gh.gamecenter.plugin.GhPlugin \ No newline at end of file