用例 APK
用例 APK 版本:爱奇艺TV版 v12.1.0
原 APK 链接:https://pan.baidu.com/s/1zNKk662TvoNoSA8NFSWdTQ?pwd=0esa
修改 APK 链接:https://pan.baidu.com/s/16xo6FN_o284lAUNThlpdrQ?pwd=94jt
本人测试环境
广告的表现形式很多,可能是一个界面(activity),可能是局部在上方或下方的一个区域视图(view)等。以下是常见广告形式:
无论怎样形式、怎样来源的广告,在本地一定需要展示出来,展示就需要广告内容载体,如界面、视图等,对于这些容器,即可以利用静态的布局,也可以动态生成布局。如果能移除这些容器、或者破坏容器生成条件就可以达到去广告的地步。
本次案例是来自于第三方 SDK 软件的广告投放,通过发送请求包,从而获取相对应的广告 ID 与资源,对于这种情况,我们可以通过定位 SDK 的初始化、广告请求、广告展示等代码,来分析其逻辑,从而找到突破点。
首先对开屏广告页面进行分析,通过MT管理器发现该广告是处在 WelcomeActivity 类中,我们直接hook 类,得到其函数调用栈。
可以猜测 showHomePage() 就是展示我们的主页了,我们逐条分析广告发生前的函数:
可以看到当当前Activity数量不等于1时,就直接调 showHomePage 函数,我们可以将这个判断改为永真,让其直接显示主页。
重打包编译签名,运行程序,已去除开屏广告:
对于开屏广告,我们可以观察应用启动的 Acitivity 顺序 (先从主入口切入Main),寻找其函数调用顺序,找到其播送广告的页面,将其逻辑更改,就可以屏蔽掉开屏广告。
首先对视频广告页面进行分析,有暂停键、静音键、详情键、持续时间、会员关闭提示…,我们可以想到:
……
本人选择剩余时间作为破解入口,通过开发者助手查到显示时间的资源 ID 是 R.id.account_ads_time_pre_ad
,搜索资源ID可得三处引用该资源。
通过 hook 分析发现在视频启动时的广告,调用的是 aux 类的函数:
分析 aux 类里使用了R.id.account_ads_time_pre_ad
的方法,找到三处,分别分析:
第一、二处均用在 Xi()
函数中,该函数主要设置广告配置及布置广告界面。
第三处位于 Xc()
函数中,根据 hook 到的函数调用栈,分析其运行过程:
上面两个函数都是对布局文件进行操作,设置其 text 或者是否可显,并没有判断去掉广告的地方,我们还有继续寻找。
对比两个函数发现,获取持续时间的函数是 getAdDuration(),我们去寻找该函数声明,发现在 com.iqiyi.video.qyplayersdk.player.QYMediaPlayerProxy
类中:
跟进到 com.mcto.player.nativemediaplayer.NativeMediaPlayerBridge 我们就可以发现,该软件是在Native层利用 mediaplay 获取视频时间信息。到这里获取剩余时间的 Java 层分析就差不多可以了。我们可以看到的是在 NativeMediaPlayerBridge
这个类中调用了众多 native 方法去获取广告的各种信息供后续操作,但是将所有的方法全修改一遍不太现实,我们需要寻找判断是否显示广告界面的地方。
根据 hook 上层类的方法调用发现,QYMediaPlayerProxy
类中存在一些可能是与加载广告界面相关的函数。
几个重要的函数分析:
我们重点分析 performBigCorePlayback
函数:
我们可以发现这个函数就是判断是否显示广告界面的函数,可以猜测只有当是VIP账户时,播放数据(playData)才为空,才会使 i = 0(广告ID为0)。
到这里我们就可以尝试进行破解了,将 if 判断修改,使之进入 i=0 的分支中。
重打包编译签名,运行程序,已去除视频广告:
分析代码后发现,广告的生成、调用、配置大部分都是在 QYMediaPlayerProxy
类中完成的,并且播放器的核心功能也有一部分在代理类中调用。
对于第三方SDK动态导入视频广告,通常会通过网络请求向广告服务器发送请求以获取广告,流程参考下方 android 广告 SDK 原理流程图,常用方法使通过动态代理,通过动态代理这样的方法有一定的好处:
进一步分析,我们可以想到广告不太会是在软件刚出来时就加上,一定是后续附加上去的功能。后续除了广告之外肯定也会陆续附加其他功能,如何做到这些功能扩展呢?这就可以用 proxy 代理类了,将播放器核心功能(播放视频)融入到代理类中,让其负责对核心功能进行扩展(如在播放视频之前添加广告)。这样既方便后续软件更新,也会使逻辑更加清晰、出错时能快速定位。
android 广告 SDK 原理流程图
[参考链接]:
Android反编译实战-去广告_安卓反编译去除广告_sam.li的博客
android广告SDK原理详解(附源码) - 爱码网 (likecs.com)
private void checkPermission() {
if
(lpt2.br(InitHelper.getInstance().checkInitPermission(this))) {
jumpToMain();
return
;
}
List
<String> checkInitPermission
=
InitHelper.getInstance().checkInitPermission(this);
androidx.core.app.aux.a(this, (String[]) checkInitPermission.toArray(new String[checkInitPermission.size()]),
1
);
}
/
/
检查初始化权限
public
List
<String> checkInitPermission(Context context) {
ArrayList<String> arrayList
=
new ArrayList();
ArrayList arrayList2
=
new ArrayList();
arrayList.add(
"android.permission.INTERNET"
);
/
/
访问网络的权限
if
(!org.qiyi.speaker.u.con.bMX()) {
arrayList.add(
"android.permission.READ_PHONE_STATE"
);
/
/
取手机状态的权限
}
arrayList.add(
"android.permission.WRITE_EXTERNAL_STORAGE"
);
/
/
写入外部存储设备的权限
arrayList.add(
"android.permission.ACCESS_NETWORK_STATE"
);
/
/
访问网络状态的权限
....
}
private void jumpToMain() {
Log.e(
"gzy"
,
"size:"
+
SpeakerApplication.getInstance().getCurrentActivitySize());
/
/
用户是否给软件授权
if
(!org.qiyi.speaker.o.con.bLa()) {
org.qiyi.speaker.o.con.a(this, this.mLisenceCallback);
/
/
显示免责声明并进行用户许可
/
/
加载splash启动页动画(没有后台进程)
}
else
if
(GuideController.INSTANCE.needShowSplashGuide()) {
showGuidePage();
}
else
{
/
/
launchMain(false);
}
}
/
/
首次打开,启动应用程序主界面
public void launchMain(final boolean z) {
/
/
如果当前Activity数量不等于
1
,那么显示主页。
if
(SpeakerApplication.getInstance().getCurrentActivitySize() !
=
1
) {
showHomePage(z);
return
;
}
/
/
注册一个启动画面的回调,请求广告并下载,当启动画面结束后, 显示广告。
com.qiyi.video.g.con.aXh().registerSplashCallback(new ISplashCallback() {
/
/
from
class
: com.qiyi.video.speaker.activity.WelcomeActivity.
2
@Override
/
/
org.qiyi.video.module.api.ISplashCallback
public void onAdAnimationStarted() {
}
@Override
/
/
org.qiyi.video.module.api.ISplashCallback
public void onAdCountdown(
int
i) {
}
@Override
/
/
org.qiyi.video.module.api.ISplashCallback
public void onAdOpenDetailVideo() {
}
@Override
/
/
org.qiyi.video.module.api.ISplashCallback
public void onAdStarted(String
str
) {
}
@Override
/
/
org.qiyi.video.module.api.ISplashCallback
public void onSplashFinished(
int
i) {
WelcomeActivity.this.showHomePage(z);
JobManagerUtils.a(new Runnable() {
/
/
from
class
: com.qiyi.video.speaker.activity.WelcomeActivity.
2.1
@Override
/
/
java.lang.Runnable
public void run() {
com.qiyi.video.qysplashscreen.ad.aux.aUv().aUE();
((ISplashScreenApi) ModuleManager.getModule(IModuleConstants.MODULE_NAME_SPLASH_SCREEN, ISplashScreenApi.
class
)).requestAdAndDownload();
}
},
500
, PageAutoScrollUtils.HANDLER_SWITCH_NEXT_TIPS_DELAY,
"splashAD_requestad"
, WelcomeActivity.TAG);
}
});
launchAppGuide();
}
private void checkPermission() {
if
(lpt2.br(InitHelper.getInstance().checkInitPermission(this))) {
jumpToMain();
return
;
}
List
<String> checkInitPermission
=
InitHelper.getInstance().checkInitPermission(this);
androidx.core.app.aux.a(this, (String[]) checkInitPermission.toArray(new String[checkInitPermission.size()]),
1
);
}
/
/
检查初始化权限
public
List
<String> checkInitPermission(Context context) {
ArrayList<String> arrayList
=
new ArrayList();
ArrayList arrayList2
=
new ArrayList();
arrayList.add(
"android.permission.INTERNET"
);
/
/
访问网络的权限
if
(!org.qiyi.speaker.u.con.bMX()) {
arrayList.add(
"android.permission.READ_PHONE_STATE"
);
/
/
取手机状态的权限
}
arrayList.add(
"android.permission.WRITE_EXTERNAL_STORAGE"
);
/
/
写入外部存储设备的权限
arrayList.add(
"android.permission.ACCESS_NETWORK_STATE"
);
/
/
访问网络状态的权限
....
}
private void jumpToMain() {
Log.e(
"gzy"
,
"size:"
+
SpeakerApplication.getInstance().getCurrentActivitySize());
/
/
用户是否给软件授权
if
(!org.qiyi.speaker.o.con.bLa()) {
org.qiyi.speaker.o.con.a(this, this.mLisenceCallback);
/
/
显示免责声明并进行用户许可
/
/
加载splash启动页动画(没有后台进程)
}
else
if
(GuideController.INSTANCE.needShowSplashGuide()) {
showGuidePage();
}
else
{
/
/
launchMain(false);
}
}
/
/
首次打开,启动应用程序主界面
public void launchMain(final boolean z) {
/
/
如果当前Activity数量不等于
1
,那么显示主页。
if
(SpeakerApplication.getInstance().getCurrentActivitySize() !
=
1
) {
showHomePage(z);
return
;
}
/
/
注册一个启动画面的回调,请求广告并下载,当启动画面结束后, 显示广告。
com.qiyi.video.g.con.aXh().registerSplashCallback(new ISplashCallback() {
/
/
from
class
: com.qiyi.video.speaker.activity.WelcomeActivity.
2
@Override
/
/
org.qiyi.video.module.api.ISplashCallback
public void onAdAnimationStarted() {
}
@Override
/
/
org.qiyi.video.module.api.ISplashCallback
public void onAdCountdown(
int
i) {
}
@Override
/
/
org.qiyi.video.module.api.ISplashCallback
public void onAdOpenDetailVideo() {
}
@Override
/
/
org.qiyi.video.module.api.ISplashCallback
public void onAdStarted(String
str
) {
}
@Override
/
/
org.qiyi.video.module.api.ISplashCallback
public void onSplashFinished(
int
i) {
WelcomeActivity.this.showHomePage(z);
JobManagerUtils.a(new Runnable() {
/
/
from
class
: com.qiyi.video.speaker.activity.WelcomeActivity.
2.1
@Override
/
/
java.lang.Runnable
public void run() {
com.qiyi.video.qysplashscreen.ad.aux.aUv().aUE();
((ISplashScreenApi) ModuleManager.getModule(IModuleConstants.MODULE_NAME_SPLASH_SCREEN, ISplashScreenApi.
class
)).requestAdAndDownload();
}
},
500
, PageAutoScrollUtils.HANDLER_SWITCH_NEXT_TIPS_DELAY,
"splashAD_requestad"
, WelcomeActivity.TAG);
}
});
launchAppGuide();
}
private void Vz() {
......
this.bPB
=
(TextView) findViewById(R.
id
.account_ads_time_pre_ad);
}
private void Xi() {
...
/
/
获取当前广告播放器的状态
BaseState currentState
=
this.mAdInvoker.getCurrentState();
/
/
获取了广告播放器的UI策略
int
adUIStrategy
=
this.mAdInvoker.getAdUIStrategy();
/
/
打印日志
com.iqiyi.video.qyplayersdk.g.aux.i(
"PLAY_SDK_AD_ROLL"
,
"{GPhoneRollAdView}"
,
" show ad UI, current state = "
, currentState,
", adUiStrategy: "
, Integer.valueOf(adUIStrategy));
/
/
设置视图的背景,根据当前广告播放器的状态来选择不同的背景资源
this.bPy.setBackgroundResource(currentState.isOnPaused() ? R.drawable.qiyi_sdk_play_ads_player : R.drawable.qiyi_sdk_play_ads_pause);
/
/
获取了当前广告的交付类型
int
i
=
this.mDeliverType;
boolean z
=
i
=
=
3
|| i
=
=
7
|| i
=
=
4
;
/
/
获取广告播放器配置
QYPlayerADConfig adConfig
=
this.mAdInvoker.getAdConfig();
int
i2
=
8
;
/
/
根据UI策略的不同值,来设置一些视图的可见性或执行一些方法,
8
不可见,
0
可见
if
(adUIStrategy
=
=
1
) {
this.bPA.setVisibility(
8
);
this.bPy.setVisibility(
8
);
this.bPF.setVisibility(
8
);
this.bPz.setVisibility(
8
);
}
else
if
(adUIStrategy
=
=
2
) {
this.bPA.setVisibility(
8
);
this.bPy.setVisibility(
8
);
this.bPz.setVisibility(
8
);
this.bSv.setVisibility(
8
);
this.bSq.setVisibility(
8
);
this.bSq.setOnTouchListener(null);
}
else
if
(adUIStrategy
=
=
3
) {
this.bPA.setVisibility(
8
);
this.bPF.setVisibility(
8
);
boolean isMute
=
isMute();
/
/
检查广告是否处于静音状态
this.bPL
=
isMute;
setAdMute(isMute, false);
}
else
{
this.bPF.setVisibility(
0
);
TextView textView
=
this.bPA;
if
(!this.mIsLand) {
i2
=
0
;
}
textView.setVisibility(i2);
boolean isMute2
=
isMute();
this.bPL
=
isMute2;
setAdMute(isMute2, false);
Xk();
}
if
(this.mDeliverType !
=
6
) {
this.bPB.setVisibility(
0
);
/
/
设置时间视图可显
}
this.bPB.setText(String.valueOf(this.mAdInvoker.getAdDuration()));
/
/
给时间视图赋值
}
private void Vz() {
......
this.bPB
=
(TextView) findViewById(R.
id
.account_ads_time_pre_ad);
}
private void Xi() {
...
/
/
获取当前广告播放器的状态
BaseState currentState
=
this.mAdInvoker.getCurrentState();
/
/
获取了广告播放器的UI策略
int
adUIStrategy
=
this.mAdInvoker.getAdUIStrategy();
/
/
打印日志
com.iqiyi.video.qyplayersdk.g.aux.i(
"PLAY_SDK_AD_ROLL"
,
"{GPhoneRollAdView}"
,
" show ad UI, current state = "
, currentState,
", adUiStrategy: "
, Integer.valueOf(adUIStrategy));
/
/
设置视图的背景,根据当前广告播放器的状态来选择不同的背景资源
this.bPy.setBackgroundResource(currentState.isOnPaused() ? R.drawable.qiyi_sdk_play_ads_player : R.drawable.qiyi_sdk_play_ads_pause);
/
/
获取了当前广告的交付类型
int
i
=
this.mDeliverType;
boolean z
=
i
=
=
3
|| i
=
=
7
|| i
=
=
4
;
/
/
获取广告播放器配置
QYPlayerADConfig adConfig
=
this.mAdInvoker.getAdConfig();
int
i2
=
8
;
/
/
根据UI策略的不同值,来设置一些视图的可见性或执行一些方法,
8
不可见,
0
可见
if
(adUIStrategy
=
=
1
) {
this.bPA.setVisibility(
8
);
this.bPy.setVisibility(
8
);
this.bPF.setVisibility(
8
);
this.bPz.setVisibility(
8
);
}
else
if
(adUIStrategy
=
=
2
) {
this.bPA.setVisibility(
8
);
this.bPy.setVisibility(
8
);
this.bPz.setVisibility(
8
);
this.bSv.setVisibility(
8
);
this.bSq.setVisibility(
8
);
this.bSq.setOnTouchListener(null);
}
else
if
(adUIStrategy
=
=
3
) {
this.bPA.setVisibility(
8
);
this.bPF.setVisibility(
8
);
boolean isMute
=
isMute();
/
/
检查广告是否处于静音状态
this.bPL
=
isMute;
setAdMute(isMute, false);
}
else
{
this.bPF.setVisibility(
0
);
TextView textView
=
this.bPA;
if
(!this.mIsLand) {
i2
=
0
;
}
textView.setVisibility(i2);
boolean isMute2
=
isMute();
this.bPL
=
isMute2;
setAdMute(isMute2, false);
Xk();
}
if
(this.mDeliverType !
=
6
) {
this.bPB.setVisibility(
0
);
/
/
设置时间视图可显
}
this.bPB.setText(String.valueOf(this.mAdInvoker.getAdDuration()));
/
/
给时间视图赋值
}
public void Xc() {
/
/
获取广告播放时长
int
adDuration
=
this.mAdInvoker.getAdDuration();
String
str
=
adDuration
+
"";
...
jv(adDuration);
/
/
判断能不能跳过广告
if
(XE()) {
XH();
}
TextView textView
=
this.bPB;
/
/
设置剩余时间
if
(textView !
=
null) {
textView.setText(
str
);
/
/
显示非VIP持续时间
}
int
i
=
this.mDeliverType;
if
(i
=
=
3
|| i
=
=
7
) {
/
/
如果交付类型是
3
或
7
(VIP广告),广告持续时间小于
1
,调用dz(false)
if
(adDuration <
1
) {
dz(false);
}
else
{
this.bSA.setText(
str
);
/
/
显示VIP持续时间
}
}
if
(this.mDeliverType
=
=
2
) {
/
/
允许跳过的广告
int
Xp
=
Xp();
/
/
广告可跳过的剩余时间
if
(Xp <
1
) {
/
/
允许跳过
Xl();
/
/
显示跳过按钮
}
else
{
this.bSG.setText(this.mContext.getString(R.string.trueview_accountime, Integer.valueOf(Xp)));
}
}
/
/
省流:根据不同的交付类型,为不同类型的广告进行时间配置与视图是否可显操作
...
}
/
/
处理广告的交互时间限制逻辑
private void jv(
int
i) {
/
/
判断是否为触摸广告,是否支持点击跳转,并且是否已经被点击过
if
(!this.bOR.isTouchAd() || this.bOR.getClickThroughType() !
=
0
|| this.bTn) {
return
;
/
/
是,直接返回
}
/
/
获取广告的预览信息
PreAD creativeObject
=
this.bOR.getCreativeObject();
/
/
getInterTouchTime()是广告中点击交互的时间间隔,返回
10
,表示用户需要等待至少
10
秒之后才能进行一次点击交互。小于
0
,说明可以点击。
/
/
后面一个条件是指当前时间加上最早允许交互的时间点,如果超过广告总时长,则不允许交互,比如总时长
120
秒,getInterTouchTime() 返回
40
,当前时间为
100
秒,大于总时长,不允许交互。
if
(creativeObject.getInterTouchTime() <
=
-
1
|| i
+
creativeObject.getInterTouchTime() > this.bTp) {
return
;
}
/
/
重置广告界面,继续播放
this.bSq.reset();
Wu();
}
/
/
判断当前广告是创意广告
private boolean XE() {
CupidAD<PreAD> cupidAD
=
this.bOR;
if
(cupidAD
=
=
null || cupidAD.getCreativeObject()
=
=
null) {
return
false;
}
return
this.bOR.getDeliverType()
=
=
10
|| this.bOR.getDeliverType()
=
=
11
;
}
/
/
计算广告可跳过的剩余时间
private
int
Xp() {
if
(this.bOR.getDeliverType() !
=
2
) {
return
0
;
}
return
(this.bOR.getSkippableTime()
/
1000
)
-
((this.bOR.getDuration()
/
1000
)
-
this.mAdInvoker.getAdDuration());
}
public void Xc() {
/
/
获取广告播放时长
int
adDuration
=
this.mAdInvoker.getAdDuration();
String
str
=
adDuration
+
"";
...
jv(adDuration);
/
/
判断能不能跳过广告
if
(XE()) {
XH();
}
TextView textView
=
this.bPB;
/
/
设置剩余时间
if
(textView !
=
null) {
textView.setText(
str
);
/
/
显示非VIP持续时间
}
int
i
=
this.mDeliverType;
if
(i
=
=
3
|| i
=
=
7
) {
/
/
如果交付类型是
3
或
7
(VIP广告),广告持续时间小于
1
,调用dz(false)
if
(adDuration <
1
) {
dz(false);
}
else
{
this.bSA.setText(
str
);
/
/
显示VIP持续时间
}
}
if
(this.mDeliverType
=
=
2
) {
/
/
允许跳过的广告
int
Xp
=
Xp();
/
/
广告可跳过的剩余时间
if
(Xp <
1
) {
/
/
允许跳过
Xl();
/
/
显示跳过按钮
}
else
{
this.bSG.setText(this.mContext.getString(R.string.trueview_accountime, Integer.valueOf(Xp)));
}
}
/
/
省流:根据不同的交付类型,为不同类型的广告进行时间配置与视图是否可显操作
...
}
/
/
处理广告的交互时间限制逻辑
private void jv(
int
i) {
/
/
判断是否为触摸广告,是否支持点击跳转,并且是否已经被点击过
if
(!this.bOR.isTouchAd() || this.bOR.getClickThroughType() !
=
0
|| this.bTn) {
return
;
/
/
是,直接返回
}
/
/
获取广告的预览信息
PreAD creativeObject
=
this.bOR.getCreativeObject();
/
/
getInterTouchTime()是广告中点击交互的时间间隔,返回
10
,表示用户需要等待至少
10
秒之后才能进行一次点击交互。小于
0
,说明可以点击。
/
/
后面一个条件是指当前时间加上最早允许交互的时间点,如果超过广告总时长,则不允许交互,比如总时长
120
秒,getInterTouchTime() 返回
40
,当前时间为
100
秒,大于总时长,不允许交互。
if
(creativeObject.getInterTouchTime() <
=
-
1
|| i
+
creativeObject.getInterTouchTime() > this.bTp) {
return
;
}
/
/
重置广告界面,继续播放
this.bSq.reset();
Wu();
}
/
/
判断当前广告是创意广告
private boolean XE() {
CupidAD<PreAD> cupidAD
=
this.bOR;
if
(cupidAD
=
=
null || cupidAD.getCreativeObject()
=
=
null) {
return
false;
}
return
this.bOR.getDeliverType()
=
=
10
|| this.bOR.getDeliverType()
=
=
11
;
}
/
/
计算广告可跳过的剩余时间
private
int
Xp() {
if
(this.bOR.getDeliverType() !
=
2
) {
return
0
;
}
return
(this.bOR.getSkippableTime()
/
1000
)
-
((this.bOR.getDuration()
/
1000
)
-
this.mAdInvoker.getAdDuration());
}
public
int
getAdDuration() {
com.iqiyi.video.qyplayersdk.core.com1 com1Var
=
this.mPlayerCore;
if
(com1Var
=
=
null) {
return
0
;
}
return
com1Var.getAdsTimeLength();
}
/
/
位于 com.iqiyi.video.qyplayersdk.core.QYBigCorePlayer 类中
public
int
getAdsTimeLength() {
com8 com8Var
=
this.pumaPlayer;
if
(com8Var !
=
null) {
return
Math.
round
(com8Var.GetADCountDown()
/
1000.0f
);
/
/
转成整数
}
return
0
;
}
/
/
com.mcto.player.nativemediaplayer.NativeMediaPlayer 类中
public
int
GetADCountDown() {
int
GetADCountDown;
if
(IsCalledInPlayerThread()) {
/
/
判断是否在播放器线程中调用
return
this.native_media_player_bridge.GetADCountDown();
/
/
获取广告持续时间
}
synchronized (this) {
if
(!this.native_player_valid) {
/
/
判断播放器是否合法
throw new MctoPlayerInvalidException(puma_state_error_msg);
}
GetADCountDown
=
this.native_media_player_bridge.GetADCountDown();
}
return
GetADCountDown;
}
/
/
com.mcto.player.nativemediaplayer.NativeMediaPlayerBridge 类中
public
int
GetADCountDown() {
/
/
调用了一个指定
ID
为
43
的方法,该方法返回一个JSON格式的字符串,其中包含有关广告信息的数据
String InvokeMethod
=
InvokeMethod(
43
,
"{}"
);
if
(InvokeMethod.isEmpty()) {
/
/
返回的字符串为空,则表示当前没有广告,方法返回
0
。
return
0
;
}
try
{
/
/
返回的字符串不为空,则将其转换为JSONObject对象,并获取其中名为ad_count_down的值
return
new JSONObject(InvokeMethod).getInt(
"ad_count_down"
);
} catch (JSONException unused) {
return
0
;
}
}
public
int
getAdDuration() {
com.iqiyi.video.qyplayersdk.core.com1 com1Var
=
this.mPlayerCore;
if
(com1Var
=
=
null) {
return
0
;
}
return
com1Var.getAdsTimeLength();
}
/
/
位于 com.iqiyi.video.qyplayersdk.core.QYBigCorePlayer 类中
public
int
getAdsTimeLength() {
com8 com8Var
=
this.pumaPlayer;
if
(com8Var !
=
null) {
return
Math.
round
(com8Var.GetADCountDown()
/
1000.0f
);
/
/
转成整数
}
return
0
;
}
/
/
com.mcto.player.nativemediaplayer.NativeMediaPlayer 类中
public
int
GetADCountDown() {
int
GetADCountDown;
if
(IsCalledInPlayerThread()) {
/
/
判断是否在播放器线程中调用
return
this.native_media_player_bridge.GetADCountDown();
/
/
获取广告持续时间
}
synchronized (this) {
if
(!this.native_player_valid) {
/
/
判断播放器是否合法
throw new MctoPlayerInvalidException(puma_state_error_msg);
}
GetADCountDown
=
this.native_media_player_bridge.GetADCountDown();
}
return
GetADCountDown;
}
/
/
com.mcto.player.nativemediaplayer.NativeMediaPlayerBridge 类中
public
int
GetADCountDown() {
/
/
调用了一个指定
ID
为
43
的方法,该方法返回一个JSON格式的字符串,其中包含有关广告信息的数据
String InvokeMethod
=
InvokeMethod(
43
,
"{}"
);
if
(InvokeMethod.isEmpty()) {
/
/
返回的字符串为空,则表示当前没有广告,方法返回
0
。
return
0
;
}
try
{
/
/
返回的字符串不为空,则将其转换为JSONObject对象,并获取其中名为ad_count_down的值
return
new JSONObject(InvokeMethod).getInt(
"ad_count_down"
);
} catch (JSONException unused) {
return
0
;
}
}
/
/
setVVCollector():设置VVCollector,收集播放器的VV统计信息。
/
/
video view (VV),意思为视频播放次数,根据广告播放次数,统计盈利。
public void setVVCollector(com.iqiyi.video.qyplayersdk.module.a.f.con conVar) {
com.iqiyi.video.qyplayersdk.module.a.aux auxVar
=
this.mStatistics;
if
(auxVar !
=
null) {
auxVar.setVVCollector(conVar);
}
}
/
/
init(): 初始化播放器界面
/
/
获取了mControlConfig中的一些配置信息,例如编解码类型、是否自动跳过片头片尾、色盲模式等,然后调用prn.aux构造方法创建一个prn对象,并设置这些配置信息,最后通过a()方法将prn对象和mPassportAdapter对象一起传入a方法中,完成播放器的初始化。
public void init() {
this.mPlayerCore.a(new prn.aux(this.mControlConfig.getCodecType())
.eH(this.mControlConfig.isAutoSkipTitle())
.eI(this.mControlConfig.isAutoSkipTrailer())
.kR(this.mControlConfig.getColorBlindnessType())
.lX(this.mControlConfig.getExtendInfo())
.lY(this.mControlConfig.getExtraDecoderInfo())
.aie(), com.iqiyi.video.qyplayersdk.core.data.aux.a(this.mPassportAdapter));
}
/
/
检查 RC 策略是否需要执行
/
/
RC 策略是指在不同的地理位置或网络环境下,根据不同的版权限制或合作协议,播放不同的内容或提供不同的服务。
public PlayData checkRcIfRcStrategyNeeded(PlayData playData) {
if
(playData
=
=
null) {
com.iqiyi.video.qyplayersdk.g.aux.d(TAG,
"QYMediaPlayerProxy checkRcIfRcStrategyNeeded source == null!"
);
return
playData;
}
int
rCCheckPolicy
=
playData.getRCCheckPolicy();
com.iqiyi.video.qyplayersdk.g.aux.d(TAG,
"QYMediaPlayerProxy checkRcIfRcStrategyNeeded strategy == "
+
rCCheckPolicy);
if
(this.mPlayerRecordAdapter
=
=
null) {
this.mPlayerRecordAdapter
=
new PlayerRecordAdapter();
}
/
/
根据 RCCheckPolicy (即 RC 策略) 的值。
/
/
如果值为
2
,直接返回 playData;如果值为
1
或
0
,,则调用 PlayerRecordAdapter 的 retrievePlayerRecord 方法,获取播放记录,
return
rCCheckPolicy
=
=
2
? playData : (rCCheckPolicy
=
=
1
|| rCCheckPolicy
=
=
0
) ?
com.iqiyi.video.qyplayersdk.player.data.b.con.a(playData, this.mPlayerRecordAdapter.retrievePlayerRecord(playData)) : playData;
}
/
/
获取登录用户信息
void login() {
IPassportAdapter iPassportAdapter;
/
/
mPlayerCore 是播放器核心,mPassportAdapter 是用户身份验证适配器。
if
(this.mPlayerCore
=
=
null || (iPassportAdapter
=
this.mPassportAdapter)
=
=
null) {
return
;
}
/
/
判断是不是VIP用户,并获取相应用户信息
this.mPlayerCore.login(com.iqiyi.video.qyplayersdk.core.data.aux.a(iPassportAdapter));
}
/
/
准备播放器重要核心配置
private void prepareBigCorePlayback(PlayData playData) {
boolean z;
org.qiyi.android.coreplayer.d.com7.beginSection(
"QYMediaPlayerProxy.prepareBigCorePlayback"
);
/
/
检查是否需要预加载
com.iqiyi.video.qyplayersdk.h.con conVar
=
this.mPreload;
if
(conVar !
=
null) {
conVar.aoj();
}
/
/
根据播放数据和控制配置,选择一个播放策略,根据策略选择对应操作
int
a2
=
com.iqiyi.video.qyplayersdk.player.data.b.nul.a(playData, this.mContext, this.mControlConfig);
com.iqiyi.video.qyplayersdk.g.aux.e(
"PLAY_SDK"
,
"vplay strategy : "
+
a2);
switch (a2) {
case
1
:
performBigCorePlayback(playData);
break
;
case
2
:
z
=
true;
doVPlayBeforePlay(playData, z);
break
;
case
3
:
doVPlayFullBeforePlay(playData);
break
;
case
4
:
doVPlayAfterPlay(playData);
break
;
case
5
:
if
(com.iqiyi.video.qyplayersdk.g.aux.isDebug()) {
throw new RuntimeException(
"address & tvid & ctype are null"
);
}
com.iqiyi.video.qyplayersdk.g.aux.e(
"PLAY_SDK"
,
"address & tvid & ctype are null"
);
break
;
case
6
:
z
=
false;
doVPlayBeforePlay(playData, z);
break
;
}
org.qiyi.android.coreplayer.d.com7.endSection();
}
/
/
视频播放结束后,继续获取视频的相关信息。
public void doVPlayAfterPlay(final PlayData playData) {
performBigCorePlayback(playData);
lpt6 lpt6Var
=
this.mTaskExecutor;
if
(lpt6Var !
=
null) {
lpt6Var.q(new Runnable() {
/
/
from
class
: com.iqiyi.video.qyplayersdk.player.QYMediaPlayerProxy.
1
@Override
/
/
java.lang.Runnable
public void run() {
QYMediaPlayerProxy.this.requestVplayInfo(playData);
}
});
}
}
/
/
在获取视频源前获取一些与视频相关的信息
private void doVPlayBeforePlay(PlayData playData, boolean z) {
VPlayParam a2
=
com.iqiyi.video.qyplayersdk.player.data.b.con.a(playData, VPlayHelper.CONTENT_TYPE_PLAY_CONDITION, this.mPassportAdapter);
this.mVPlayHelper.cancel();
/
/
请求 VPlay 信息
this.mVPlayHelper.requestVPlay(this.mContext, a2, new aux(this, playData, this.mSigt, z), this.mBigcoreVplayInterceptor);
sendVPlayRequestPingback(true, playData, this.mSigt);
com.iqiyi.video.qyplayersdk.b.com3.b(playData);
com.iqiyi.video.qyplayersdk.g.aux.d(
"PLAY_SDK"
, TAG,
" doVPlayBeforePlay needRequestFull="
, Boolean.valueOf(z));
}
/
/
判断是否需要网络拦截
private boolean isNeedNetworkInterceptor(PlayerInfo playerInfo) {
/
/
是否需要忽略用户代理的拦截
if
(ignoreNetworkInterceptByUA()) {
com.iqiyi.video.qyplayersdk.g.aux.d(
"PLAY_SDK"
, TAG,
"ignoreNetworkInterceptByUA "
);
return
false;
}
/
/
判断当前是否处于离线状态,并且要播放的视频是在线视频
boolean gW
=
org.iqiyi.video.l.aux.gW(this.mContext);
boolean D
=
com.iqiyi.video.qyplayersdk.player.data.b.nul.D(playerInfo);
if
(gW && D) {
/
/
获取当前的错误码版本号,根据不同的版本号来执行不同的逻辑
int
errorCodeVersion
=
getErrorCodeVersion();
com.iqiyi.video.qyplayersdk.g.aux.d(
"PLAY_SDK"
, TAG,
"isNeedNetworkInterceptor isOffNetWork = "
, Boolean.valueOf(gW),
" isOnLineVideo = "
, Boolean.valueOf(D),
" errorCodeVer = "
+
errorCodeVersion);
if
(errorCodeVersion
=
=
1
) {
/
/
自定义错误码为
900400
的播放器错误
this.mInvokerQYMediaPlayer.onError(PlayerError.createCustomError(
900400
,
"current network is offline, but you want to play online video"
));
return
true;
/
/
进行网络拦截
}
else
if
(errorCodeVersion
=
=
2
) {
/
/
返回错误码和错误信息
org.iqiyi.video.data.com7 bbQ
=
org.iqiyi.video.data.com7.bbQ();
bbQ.xC(String.valueOf(
900400
));
bbQ.setDesc(
"current network is offline, but you want to play online video"
);
this.mInvokerQYMediaPlayer.onErrorV2(bbQ);
return
true;
}
}
return
false;
/
/
不需要进行网络拦截
}
/
/
setVVCollector():设置VVCollector,收集播放器的VV统计信息。
/
/
video view (VV),意思为视频播放次数,根据广告播放次数,统计盈利。
public void setVVCollector(com.iqiyi.video.qyplayersdk.module.a.f.con conVar) {
com.iqiyi.video.qyplayersdk.module.a.aux auxVar
=
this.mStatistics;
if
(auxVar !
=
null) {
auxVar.setVVCollector(conVar);
}
}
/
/
init(): 初始化播放器界面
/
/
获取了mControlConfig中的一些配置信息,例如编解码类型、是否自动跳过片头片尾、色盲模式等,然后调用prn.aux构造方法创建一个prn对象,并设置这些配置信息,最后通过a()方法将prn对象和mPassportAdapter对象一起传入a方法中,完成播放器的初始化。
public void init() {
this.mPlayerCore.a(new prn.aux(this.mControlConfig.getCodecType())
.eH(this.mControlConfig.isAutoSkipTitle())
.eI(this.mControlConfig.isAutoSkipTrailer())
.kR(this.mControlConfig.getColorBlindnessType())
.lX(this.mControlConfig.getExtendInfo())
.lY(this.mControlConfig.getExtraDecoderInfo())
.aie(), com.iqiyi.video.qyplayersdk.core.data.aux.a(this.mPassportAdapter));
}
/
/
检查 RC 策略是否需要执行
/
/
RC 策略是指在不同的地理位置或网络环境下,根据不同的版权限制或合作协议,播放不同的内容或提供不同的服务。
public PlayData checkRcIfRcStrategyNeeded(PlayData playData) {
if
(playData
=
=
null) {
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2023-4-7 12:51
被I鲸落I编辑
,原因: