首页
社区
课程
招聘
[原创]某艺TV版 apk 破解去广告及源码分析
发表于: 2023-4-7 12:50 67653

[原创]某艺TV版 apk 破解去广告及源码分析

2023-4-7 12:50
67653

用例 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) {    // 如果交付类型是37 (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) {    // 如果交付类型是37 (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() {
        // 调用了一个指定ID43的方法,该方法返回一个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() {
        // 调用了一个指定ID43的方法,该方法返回一个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编辑 ,原因:
收藏
免费 30
支持
分享
最新回复 (12)
雪    币: 1671
活跃值: (215852)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
2
tql
2023-4-7 16:01
0
雪    币: 3535
活跃值: (31011)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2023-4-7 16:16
1
雪    币: 34
活跃值: (256)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2023-4-7 20:10
0
雪    币: 10070
活跃值: (4471)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2023-4-8 11:54
0
雪    币: 9172
活跃值: (6395)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6
不知道法务部什么时候看到这个帖子,然后要求删帖
2023-4-8 12:28
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
支持一下 很详细
2023-4-9 15:45
0
雪    币: 690
活跃值: (1841)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
8
不错支持下
2023-4-11 15:47
0
雪    币: 36207
活跃值: (7170)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
图片全挂了
2023-4-11 21:37
0
雪    币: 225
活跃值: (271)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
感谢分享,点赞
2023-4-12 17:15
0
雪    币: 2
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
哈哈哈哈,这帖子势必活不久,但是感谢分享!已经学会了
2023-4-13 10:24
0
雪    币: 3812
活跃值: (4377)
能力值: (RANK:215 )
在线值:
发帖
回帖
粉丝
12

在乐视TV上测试了下:

1、输入法没法自动调出。

2、启动能显示推荐,页面上的可以看,如果换到别的页面就提示网络错误,重新链接网络的样子,切换到推荐也不行。

3、还碰到最下面一个什么开通会员去广告的绿色背景白色字的条,然后有闪退。


另外,有没有奇异果的?



在雷电模拟器上测试,同样的问题

最后于 2023-5-29 21:03 被china编辑 ,原因: 修改
2023-5-29 20:45
0
雪    币: 713
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
链接失效了呢,可以在发一次吗
2024-11-19 15:28
0
游客
登录 | 注册 方可回帖
返回
//