diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 6863b14..257e287 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -7,11 +7,11 @@ <deviceKey> <Key> <type value="VIRTUAL_DEVICE_PATH" /> - <value value="C:\Users\moli\.android\avd\13.avd" /> + <value value="C:\Users\moli\.android\avd\11.avd" /> </Key> </deviceKey> </Target> </targetSelectedWithDropDown> - <timeTargetWasSelectedWithDropDown value="2022-12-26T07:26:54.811470200Z" /> + <timeTargetWasSelectedWithDropDown value="2023-01-02T12:00:59.533203600Z" /> </component> </project> \ No newline at end of file diff --git a/.idea/saveactions_settings.xml b/.idea/saveactions_settings.xml index ecfc35a..58b2929 100644 --- a/.idea/saveactions_settings.xml +++ b/.idea/saveactions_settings.xml @@ -7,7 +7,6 @@ <option value="activateOnShortcut" /> <option value="organizeImports" /> <option value="reformat" /> - <option value="customUnqualifiedStaticMemberAccess" /> <option value="missingOverrideAnnotation" /> <option value="unnecessaryThis" /> <option value="finalPrivateMethod" /> diff --git a/app/build.gradle b/app/build.gradle index e61186d..af15c2f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,10 +10,15 @@ android { applicationId "com.molihua.hlbmerge" minSdk 21 targetSdk 33 - versionCode 39 - versionName "1.6.0" + versionCode 42 + versionName "1.6.3 beta" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + ndk { + abiFilters "armeabi-v7a", "arm64-v8a", "x86_64" + } + } buildTypes { @@ -31,23 +36,28 @@ android { dependencies { implementation fileTree(dir: "libs", includes: ["*.aar", "*.jar"]) //文件选择器(包含权限请求) - implementation 'io.github.molihuan:pathselector:1.1.6' + implementation 'io.github.molihuan:pathselector:1.1.13' //RxFFmpeg依赖 implementation 'com.github.microshow:RxFFmpeg:4.9.0-lite' - //bugly依赖 - implementation 'com.tencent.bugly:crashreport_upgrade:latest.release' - implementation 'com.tencent.bugly:nativecrashreport:latest.release' // 腾讯的键值对存储mmkv implementation 'com.tencent:mmkv:1.2.14' //DKVideoPlayer内部默认使用系统mediaplayer进行解码 implementation 'xyz.doikki.android.dkplayer:dkplayer-java:3.3.7' + //DKVideoPlayer UI(主要是使用手势控制视图GestureView) + implementation 'xyz.doikki.android.dkplayer:dkplayer-ui:3.3.7' + //bilibili弹幕组件 + implementation 'com.github.ctiao:DanmakuFlameMaster:0.9.25' + implementation 'com.github.ctiao:ndkbitmap-armv7a:0.9.21' + implementation 'com.github.ctiao:ndkbitmap-x86:0.9.21' + //XUpdate + implementation 'com.github.xuexiangjys:XUpdate:2.1.2' + implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-easy:1.0.1' //XUI implementation 'com.github.xuexiangjys:XUI:1.2.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'com.github.bumptech.glide:glide:4.12.0' //XUI - implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' @@ -55,9 +65,5 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - //bilibili弹幕组件 -// implementation 'com.github.ctiao:DanmakuFlameMaster:0.9.25' -// implementation 'com.github.ctiao:ndkbitmap-armv7a:0.9.21' -// implementation 'com.github.ctiao:ndkbitmap-armv5:0.9.21' -// implementation 'com.github.ctiao:ndkbitmap-x86:0.9.21' + } \ No newline at end of file diff --git "a/app/release/1.6.0\345\260\235\351\262\234\347\211\210.apk" b/app/release/1.6.3 beta.apk similarity index 69% rename from "app/release/1.6.0\345\260\235\351\262\234\347\211\210.apk" rename to app/release/1.6.3 beta.apk index 836c46a..e29548f 100644 Binary files "a/app/release/1.6.0\345\260\235\351\262\234\347\211\210.apk" and b/app/release/1.6.3 beta.apk differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index 641761f..536c35a 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 39, - "versionName": "1.6.0", + "versionCode": 42, + "versionName": "1.6.3 beta", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 872ce64..54a8e6a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,11 +7,11 @@ android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" - android:icon="@mipmap/ic_launcher" + android:icon="@drawable/ml130" android:label="@string/app_name" - android:roundIcon="@mipmap/ic_launcher_round" + android:roundIcon="@drawable/ml130" android:supportsRtl="true" - android:theme="@style/Theme.Hlbmerge2" + android:theme="@style/Theme.Hlbmerge" tools:targetApi="31"> <activity android:name=".activity.impl.MainActivity" @@ -26,6 +26,12 @@ android:name="android.app.lib_name" android:value="" /> </activity> + <activity android:name=".activity.impl.SettingsActivity" /> + <activity android:name=".activity.impl.AboutActivity" /> + <activity android:name=".activity.impl.HtmlActivity" /> + <activity + android:name=".activity.impl.PlayVideoActivity" + android:configChanges="orientation|screenSize" /> </application> </manifest> \ No newline at end of file diff --git a/app/src/main/assets/openSourceLicense.html b/app/src/main/assets/openSourceLicense.html new file mode 100644 index 0000000..c10cb5a --- /dev/null +++ b/app/src/main/assets/openSourceLicense.html @@ -0,0 +1,49 @@ +<!doctype html> +<html> +<head> + <meta charset='UTF-8'> + <title>开源许可</title> +</head> +<body> +<div> + <h2><span>特别感谢以下开源项目以及其依赖项目</span></h2> + + <h3><span>RxFFmpeg</span></h3> + <p> + <span>项目地址:</span> + <a href="https://github.com/microshow/RxFFmpeg">https://github.com/microshow/RxFFmpeg</a> + </p> + + <h3><span>DanmakuFlameMaster</span></h3> + <p> + <span>项目地址:</span> + <a href="https://github.com/bilibili/DanmakuFlameMaster">https://github.com/bilibili/DanmakuFlameMaster</a> + </p> + + <h3><span>XUI</span></h3> + <p> + <span>项目地址:</span> + <a href="https://github.com/xuexiangjys/XUI">https://github.com/xuexiangjys/XUI</a> + </p> + + <h3><span>MMKV</span></h3> + <p> + <span>项目地址:</span> + <a href="https://github.com/Tencent/MMKV">https://github.com/Tencent/MMKV</a> + </p> + + <h3><span>mlhfileselectorlib</span></h3> + <p> + <span>项目地址:</span> + <a href="https://github.com/molihuan/mlhfileselectorlib">https://github.com/molihuan/mlhfileselectorlib</a> + </p> + + <h3><span>DKVideoPlayer</span></h3> + <p> + <span>项目地址:</span> + <a href="https://github.com/Doikki/DKVideoPlayer">https://github.com/Doikki/DKVideoPlayer</a> + </p> + +</div> +</body> +</html> diff --git a/app/src/main/assets/privacy.html b/app/src/main/assets/privacy.html new file mode 100644 index 0000000..96ef9ad --- /dev/null +++ b/app/src/main/assets/privacy.html @@ -0,0 +1,97 @@ +<!DOCTYPE html> +<!-- saved from url=(0036)http://www.moji.com/about/agreement/ --> +<html><head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>隐私政策</title> +<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"> +<meta content="yes" name="apple-mobile-web-app-capable"> +<meta content="yes" name="apple-touch-fullscreen"> +<meta name="baidu-site-verification" content="fmADvr1NSK"> +<meta name="description" content="隐私政策"> +<meta name="keywords" content="隐私政策"> +<link rel="shortcut icon" href="https://www.ntcyan.com/favicon.ico"> + +<!--[if It IE 9]> +<script type="text/javascript" src="/templets/mojichina/js/html5shiv.js"></script> +<![endif]--> + + <style type="text/css"> + @media screen and (max-width: 640px){ + html{-webkit-text-size-adjust:100%;} + header .nav-list{ + display: none; + } + header .user-panel{ + display: none; + } + header h1 { + margin-left: 10px; + } + .main-box.server-agreement{ + margin: 10px 10px 10px; + padding-top: 0; + /*padding: 10px;*/ + } + .server-agreement .wrap{ + width: auto; + } + .server-agreement .common-pannel{ + padding: 10px; + } + .second-page .footer{ + padding: 10px; + } + .footer .footer-nav{ + width: auto; + } + .footer .footer-nav ul li{ + line-height: 26px; + } + + } + </style> +</head> +<body class="second-page"> + <div class="main-box server-agreement"> + <div class="wrap"> + <div class="common-pannel"> + <h2>隐私政策</h2> + <div class="text"> + <p>开发者和他的小伙伴们(以下称"我们")隐私权保护声明保护用户个人隐私的承诺。鉴于移动产品的特性, "我们将无可避免地与您产生直接或间接的互动关系,故特此说明我们对用户个人信息所采取的收集、使用和保护政策,请您务必仔细阅读!</p> + <h3>更新日期</h3> + <p>2022 年 5 月 4 日</p> + <h3>个人信息的收集和使用</h3> + <p>个人信息是可用于唯一地识别或联系某人的数据。</p> + <p>你在使用此工具时,可能会要求提供你的个人信息。我们会严格按本隐私政策使用该信息。我们还可将此信息与其他信息合并在一起,用于提供和改进我们的产品、服务、内容。</p> + <p>下文是我们可能收集的个人信息的类型以及我们如何使用该信息的一些示例。</p> + <h3>我们使用何种权限</h3> + <p>当你下载并使用“HLB站缓存合并”时,我们会使用几种权限,包括你的存储权限、Android/data目录访问权限、设备信息等。</p> + <p>存储权限:用于合并数据的处理、包括创建文件、修改文件、复制文件、删除文件等操作;</p> + <p>Android/data目录访问权限:用于读取bilibili缓存数据;</p> + <p>设备信息:用于获取设备基本信息主要是崩溃信息用于分析和修复bug以改进工具,这部分信息由腾讯Bugly统计SDK获取,所以你必须遵守其隐私政策。</p> + <h3>我们如何使用你的个人信息</h3> + <p>我们使用个人信息帮助我们开发、交付和改进我们的产品、服务、内容。</p> + <p>我们会不时地使用你的个人信息以帮助开发者排查崩溃问题,帮助APP提升稳定性。由于此信息对你与 我们 之间的互动至关重要,你不能放弃接收此类沟通信息。</p> + <h3>第三方SDK</h3> + <p>我们可能会提供从我们的网站链接或连接到第三方网站或服务,对第三方网站的隐私做法或内容,我们不予负责。</p> + <p>腾讯BuglySDK</p> + <p>腾讯BuglySDK获取了设备品牌、型号、软件系统版本等等相关信息,用于帮助开发者排查崩溃问题,帮助APP提升稳定性</p> + <p>腾讯BuglySDK隐私政策地址:https://privacy.qq.com/document/preview/fc748b3d96224fdb825ea79e132c1a56</p> + <h3>隐私问题</h3> + <p>如果你对我们的隐私政策或数据处理有任何问题或顾虑,请联系我们(邮箱:1492906929@qq.com)。</p> + <p>我们可随时对隐私政策加以更新。我们保留对其的最终解释权。</p> + </div> + + </div> + </div> + </div> + + <footer class="footer clearfix"> + <div class="copyright" id="cr"> + <p>Copyright © 2019-2023molihuan All Right Reserved </p> + </div> + +</footer> + + +</body></html> \ No newline at end of file diff --git a/app/src/main/assets/statement.txt b/app/src/main/assets/statement.txt new file mode 100644 index 0000000..9377cbd --- /dev/null +++ b/app/src/main/assets/statement.txt @@ -0,0 +1,64 @@ + 本《声明》包括:特别鸣谢和《软件协议》。 + 一、特别鸣谢 + 为本软件开发提供重要帮助的开源项目以及其依赖项目和用户: + 【ffmpeg开源项目】:此软件是基于ffmpeg进行开发的。 + 【RxFFmpeg开源项目】:感谢microshow提供的ffmpeg开源解决方案。 + 【CSDN】:作者从网站上查找了大量的资料,解决了许多的问题。 + 【云注入】:感谢云注入曾经提供的网络弹窗技术支持。(现已不用) + 【Bugly】:感谢腾讯提供的技术服务。 + 【MMKV】:感谢腾讯提供的开源项目。 + 【XUI】:感谢xuexiangjys提供的开源UI框架。 + 【XXPermissions】【EasyHttp】:感谢getActivity提供的开源权限请求和网络框架。 + 【AndroidUtilCode】:感谢Blankj提供的开源utils。 + 【DanmakuFlameMaster】:感谢bilibili提供的开源项目。 + 【DKVideoPlayer】:感谢Doikki提供的开源播放器项目。 + 等等 + 非常感谢以下网友帮助测试反馈Bug并提出建议: + 【@B站:wtybilibiliwty】 + 【@B站:会飞的梧鼠】 + 【@B站:Link_bbj】 + 【@基安:神马都是神马】 + 【@基安:dreampjk】 + 【@大灰辉】 + 【@B站:兵封千里】 + 【@B站:残情义梦】 + 【@B站:Cugires】 + 【@B站:时光流逝124】 + 【@B站:HAPPY-YIFAN】 + 【@基安:qazplmqwe】 + 【@B站:啵啵虎超帅】 + 【@B站:欧皇护体这个人】 + 【@爱吾:天涯的芳草】 + 【@B站:蒙古上单一】 + 【@B站:偶然忆起曾经】 + 【@B站:孤云闲人】 + 【@B站:冰色铠甲】 + 【@基安:氿洛】 + 【@B站:Ayakuzihs】 + 【@基安:zZ不是酱油】 + 【@B站:羽翼已丰】 + 【@B站:jangdoo】 + 【@B站:慈爱の勇者】 + 【@俊总】:感谢俊总提供大会员账号,用于下载测试数据 + 【@舒总】:感谢舒总提供机型用于测试 + 【@HL】:帮助测试并反馈Bug + 【@欣】:提供部分设计思路,帮助测试反馈了大量的bug并提出建议。 + 【@娜】:设计软件图标,提供部分图片资源。 + 【@bilibili官方】:部分小图标资源。 + 等等(还有很多朋友,篇幅有限) + 《软件协议》描述我们与您之间关于本软件许可使用及相关方面的权利义务。请您务必审慎阅读、充分理解各条款内容,以及知晓隐私政策,并选择接受或不接受(未成年人应在法定监护人陪同下审阅)。除非您接受本《软件协议》条款,否则您无权下载、安装或使用本软件及其相关服务。您的安装、使用行为将视为对本《协议》的接受,并同意接受本《协议》各项条款的约束。 + + 二、《软件协议》 + 【关于本软件】此软件是由我们开发的一款将B站缓存进行合并的小工具,这里的"我们"是指所有直接或间接参与软件开发的人员以及测试和反馈Bug的网友、提供资源的朋友同学、此项目依赖或间接依赖的项目。感谢你们的帮助和陪伴。 + 【用户禁止行为】除非我们书面许可,否则您不得从事下列行为: + (1)删除本软件及其副本上关于原作者著作权的信息。 + (2)用于商业用途。 + 【法律责任与免责】 + (1)本软件仅用于学习和技术交流,严禁用于商业用途!严禁用于商业用途!严禁用于商业用途!造成的损失原作者不承担任何责任。 + (2)可以进行二次修改,但不可删除本软件及其副本上关于原作者著作权的信息,二次修改后的软件造成您的损失原作者不承担任何责任。 + (3)如此软件侵犯到您的权益请联系邮箱:1492906929@qq.com,我们会进行处理,给您带来不便我深感抱歉。 + (4)在使用本软件前您必须知晓本软件的软件协议和隐私条款,并选择接受或不接受,当您的安装、使用行为将视为对软件协议和隐私条款的接受,并同意接受软件协议和隐私条款的各项条款的约束。 + 【其他条款】 + (1)电子文本形式的授权协议和其附属的隐私政策如同双方书面签署的协议一样,具有完全的和等同的法律效力。您使用本软件或本服务即视为您已阅读并同意受本协议的约束。协议许可范围以外的行为,将直接违反本授权协议并构成侵权,我们有权随时终止授权,责令停止损害,并保留追究相关责任的权力。 + (2)我们有权在必要时修改本协议条款。您可以在本软件的最新版本中查阅相关协议条款。本协议条款变更后,如果您继续使用本软件,即视为您已接受修改后的协议。如果您不接受修改后的协议,应当停止使用本软件。 + (3)我们保留对本协议的最终解释权。 \ No newline at end of file diff --git a/app/src/main/assets/updataLog.html b/app/src/main/assets/updataLog.html new file mode 100644 index 0000000..bedf8c0 --- /dev/null +++ b/app/src/main/assets/updataLog.html @@ -0,0 +1,168 @@ +<!doctype html> +<html> +<head> + <meta charset='UTF-8'> + <title>更新日志</title> + + +</head> +<body> +<div> + <h2> + <span>【安卓11用户注意】:不要一次合并太多视频,安卓11用户需要2倍合并视频的空间才可合并,实际上是把你选择的视频缓存复制出Android/data进行处理</span> + </h2> + <h2><span>一些机型使用此软件可能有问题,具体问题请自测!!!</span></h2> + <h2> + <span>已知可能存在问题的机型:【荣耀畅玩8C】【荣耀Note10 RVL-AL09】【荣耀平板5 JDN2-W09HN】【Redmi Note10 Pro】【红米K40 M2012K11AC】【MI 8】、【红米K20 Pro】、【红米10X系列】</span> + </h2> + + <h3><span>因为权限问题,输出路径不能设置为SD卡,缓存文件路径可以设置为SD卡,即SD卡只能读取不能写入</span></h3> + + + <h3><span>1.6.3 beta版本(新)</span></h3> + <h4><span>更新日期:2023年1月2日</span></h4> + <p><span>----播放器优化,新增弹幕设置(大小、速度、透明度)</span></p> + <p><span>----细节优化</span></p> + + <h3><span>1.6.2 beta版本(新)</span></h3> + <h4><span>更新日期:2022年12月29日</span></h4> + <p><span>----优化细节</span></p> + + <h3><span>1.6.0版本(新)</span></h3> + <h4><span>更新日期:2022年12月25日</span></h4> + <p><span>----重构项目</span></p> + <p><span>----增加稳定性</span></p> + <p><span>----优化内部处理逻辑</span></p> + <p><span>----适配Android 13</span></p> + <p><span>----不再支持blv格式的缓存视频合并(如果需要合并blv格式请使用1.6.0以前的版本)</span></p> + + <h3><span>1.5.8版本(新)</span></h3> + <h4><span>更新日期:2022年8月13日</span></h4> + <p><span>----自定义完成路径</span></p> + <p><span>----优化了一些用户体验</span></p> + + <h3><span>1.5.6版本(新)</span></h3> + <h4><span>更新日期:2022年6月13日</span></h4> + <p><span>----修复因数据加载失败而导致崩溃的问题</span></p> + <p><span>----开放自定义选择缓存解析路径</span></p> + <p><span>----支持SD卡(只有读的权限没有写的权限)</span></p> + <p><span>----去除"x86" 平台架构</span></p> + <p><span>----新增更新弹幕,根据AV或BV号下载弹幕、封面</span></p> + + <h3><span>1.5.4版本(新)</span></h3> + <h4><span>更新日期:2022年5月13日</span></h4> + <p><span>----修复部分机型闪退问题</span></p> + <p><span>----适配哔哩哔哩HD 包名:tv.danmaku.bilibilihd</span></p> + <p><span>----适配哔哩哔哩概念 包名:com.bilibili.app.blue</span></p> + + <h3><span>1.5.3版本(新)</span></h3> + <h4><span>更新日期:2022年5月11日</span></h4> + <p><span>----修复部分机型安装不上的bug</span></p> + <p><span>----修复无法授权的bug</span></p> + + <h3><span>1.5.2版本(新)</span></h3> + <h4><span>更新日期:2022年5月10日</span></h4> + <p><span>----新增若干未知bug</span></p> + + <h3><span>1.5.1版本(新)</span></h3> + <h4><span>更新日期:2022年5月10日</span></h4> + <p><span>----修复已知若干bug</span></p> + + <h3><span>1.5.0版本(新)</span></h3> + <h4><span>更新日期:2022年5月5日</span></h4> + <p><span>----新增挂载弹幕视频播放功能(应网友和粉丝要求)</span></p> + <p><span>----新增若干未知bug (-_-)</span></p> + + <h3><span>1.4.9版本(新)</span></h3> + <h4><span>更新日期:2022年5月4日</span></h4> + <p><span>----祝广大青年朋友节日快乐</span></p> + <p><span>----重构项目(以前的版本太乱已放弃维护)</span></p> + <p><span>----修改包名为:com.molihua.hlbmerge</span></p> + <p><span>----在旧版本的基础上修复一些bug</span></p> + <p><span>----添加、更新依赖</span></p> + <p><span>----新增视频播放功能(未来可能会添加弹幕解析)</span></p> + <p><span>----新增执行ffmpeg命令功能</span></p> + <p><span>----新增单独提取缓存视频(无声)、音频功能</span></p> + + <h3><span>1.4.4版本</span></h3> + <h4><span>更新日期:2021年10月18日</span></h4> + <p><span>----支持哔哩哔哩Play版本(设置里开启)</span></p> + <p><span>----修复旧版blv合并闪退问题</span></p> + + <h3><span>1.4.3.2版本</span></h3> + <h4><span>更新日期:2021年10月7日</span></h4> + <p><span>----修复获取路径问题,提高兼容性</span></p> + <p><span>----到此应该能适配市面上的大多数机型了</span></p> + + <h3><span>1.4.1版本(测试版)</span></h3> + <h4><span>更新日期:2021年10月5日</span></h4> + <p><span>----适配低版本,支持Android 5.0 ~ 11</span></p> + <p><span>----修复若干bug</span></p> + <p><span>----目前还有重复合并的bug以后修</span></p> + + <h3><span>1.4.0版本</span></h3> + <h4><span>更新日期:2021年10月1日</span></h4> + <p><span>----新增支持 "armeabi-v7a","arm64-v8a","x86","x86_64" 平台架构(旧版本只支持"armeabi-v7a")</span></p> + <p><span>----接入腾讯Bugly,在此非常感谢云注入提供的服务!非常感谢!</span></p> + <p><span>----优化单项合并时进度的加载,批量合并暂时还不支持</span></p> + <p><span>----修复搜索功能的若干bug</span></p> + <p><span>----修复刷新bug</span></p> + + <h3><span>1.3.2.2版本</span></h3> + <h4><span>更新日期:2021年9月27日</span></h4> + <p><span>----修复搜索框不加载的bug</span></p> + + <h3><span>1.3.2.1版本</span></h3> + <h4><span>更新日期:2021年9月26日</span></h4> + <p><span>----新增分P合并</span></p> + <p><span>----优化多选框的位置</span></p> + <p><span>----优化部分UI</span></p> + <p><span>----搜索功能还有bug,嗯嗯~~~~</span></p> + <p><span>----修复部分Toast提示bug</span></p> + + <h3><span>1.3.1版本</span></h3> + <h4><span>更新日期:2021年9月20日</span></h4> + <p><span>----新增更新日志</span></p> + <p><span>----优化拆分合并等待,增加用户体验</span></p> + <p><span>----新增导出xml弹幕文件,推荐使用“弹弹play”看弹幕,软件下载请自行百度</span></p> + <p><span>----优化内部逻辑</span></p> + <p><span>----优化关于我们界面</span></p> + + <h3><span>1.3.0版本</span></h3> + <h4><span>更新日期:2021年9月18日</span></h4> + <p><span>----重写UI(网友说太丑了,我太难了)ps(原谅我的配色)</span></p> + <p><span>----新增文件拆分合并功能</span></p> + <p><span>----修复长按多选混乱bug</span></p> + <p><span>----优化操作,真正的无门槛</span></p> + <p><span>----新增安卓11全文件授权,不授权也可以运行则可以不授权(主要是为了适配一些奇怪的机型)</span></p> + + <h3><span>1.2.2版本</span></h3> + <h4><span>更新日期:2021年8月26日</span></h4> + <p><span>----修复长按多选的一些逻辑bug</span></p> + <p><span>----固定白天模式</span></p> + <p><span>----修复相同名字视频无法合并的bug</span></p> + <p><span>----优化合并判断逻辑</span></p> + + <h3><span>1.2.1版本</span></h3> + <h4><span>更新日期:2021年8月23日</span></h4> + <p><span>----添加搜索功能</span></p> + <p><span>----添加长按多选功能</span></p> + <p><span>----优化了一些UI(对于这方面实在没天赋)</span></p> + + <h3><span>1.2.0版本</span></h3> + <h4><span>更新日期:2021年8月9日</span></h4> + <p><span>----支持Android 11</span></p> + + <h3><span>1.1.0版本</span></h3> + <h4><span>更新日期:2021年8月07日</span></h4> + <p><span>----新增自定义选择缓存文件位置</span></p> + <p><span>----支持旧版(blv)合并</span></p> + + <h3><span>1.0.0版本</span></h3> + <h4><span>更新日期:2021年8月06日</span></h4> + <p><span>----最基础的B站缓存合并功能</span></p> + <p><span>----支持最新版B站的缓存(m4s),暂不支持旧版(blv)</span></p> + +</div> +</body> +</html> diff --git a/app/src/main/java/com/molihua/hlbmerge/App.java b/app/src/main/java/com/molihua/hlbmerge/App.java index f5e38e9..b14d779 100644 --- a/app/src/main/java/com/molihua/hlbmerge/App.java +++ b/app/src/main/java/com/molihua/hlbmerge/App.java @@ -1,13 +1,19 @@ package com.molihua.hlbmerge; + import android.app.Application; +import android.widget.Toast; import com.molihua.hlbmerge.dao.ConfigData; import com.molihuan.pathselector.PathSelector; import com.molihuan.pathselector.configs.PathSelectorConfig; -import com.tencent.bugly.Bugly; +import com.molihuan.pathselector.utils.Mtools; import com.tencent.mmkv.MMKV; import com.xuexiang.xui.XUI; +import com.xuexiang.xupdate.XUpdate; +import com.xuexiang.xupdate.entity.UpdateError; +import com.xuexiang.xupdate.listener.OnUpdateFailureListener; +import com.xuexiang.xupdate.utils.UpdateUtils; import io.microshow.rxffmpeg.RxFFmpegInvoke; @@ -20,22 +26,47 @@ public class App extends Application { @Override public void onCreate() { + //腾讯的键值对存储mmkv初始化 + MMKV.initialize(this); + //配置初始化 + ConfigData.init(); //初始化XUI XUI.init(this); XUI.debug(false); - //腾讯的键值对存储mmkv初始化 - MMKV.initialize(this); //ffmpeg debug RxFFmpegInvoke.getInstance().setDebug(false); - //bugly初始化 - Bugly.init(getApplicationContext(), "ac467503ed", false); - //配置初始化 - ConfigData.init(); //路径选择器debug PathSelector.setDebug(true); //取消自动申请权限 PathSelectorConfig.setAutoGetPermission(false); + initXUpdate(); + super.onCreate(); } + + private void initXUpdate() { + XUpdate.get() + .debug(true) + .isWifiOnly(true) //默认设置只在wifi下检查版本更新 + .isGet(true) //默认设置使用get请求检查版本 + .isAutoMode(false) //默认设置非自动模式,可根据具体使用配置 + .param("versionCode", UpdateUtils.getVersionCode(this)) //设置默认公共请求参数 + .param("appKey", getPackageName()) + .setOnUpdateFailureListener(new OnUpdateFailureListener() { //设置版本更新出错的监听 + @Override + public void onFailure(UpdateError error) { + int errorCode = error.getCode(); + if (errorCode == UpdateError.ERROR.CHECK_NO_NEW_VERSION) { + Mtools.toast("未发现新版本!"); + } else { + Mtools.toast("更新失败!请自行进入下载:https://gitee.com/molihuan/BilibiliCacheVideoMergeAndroid", Toast.LENGTH_LONG); + } + } + }) + .supportSilentInstall(true) //设置是否支持静默安装,默认是true + ; + } + + } diff --git a/app/src/main/java/com/molihua/hlbmerge/activity/AbstractMainActivity.java b/app/src/main/java/com/molihua/hlbmerge/activity/AbstractMainActivity.java index ab1e35a..b3bc434 100644 --- a/app/src/main/java/com/molihua/hlbmerge/activity/AbstractMainActivity.java +++ b/app/src/main/java/com/molihua/hlbmerge/activity/AbstractMainActivity.java @@ -1,12 +1,18 @@ package com.molihua.hlbmerge.activity; import androidx.drawerlayout.widget.DrawerLayout; +import androidx.viewpager.widget.ViewPager; import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.navigation.NavigationView; +import com.molihua.hlbmerge.fragment.AbstractMainFfmpegFragment; import com.molihua.hlbmerge.fragment.AbstractMainFileShowFragment; +import com.molihua.hlbmerge.fragment.AbstractMainHandleFragment; +import com.molihua.hlbmerge.fragment.AbstractMainTitlebarFragment; +import com.molihua.hlbmerge.fragment.impl.MainCompleteFragment; import com.molihua.hlbmerge.interfaces.IMainFileShowFragment; import com.molihua.hlbmerge.interfaces.IMainTitlebarFragment; +import com.molihuan.pathselector.fragment.impl.PathSelectFragment; /** * @ClassName: AbstractMainActivity @@ -21,9 +27,23 @@ public abstract class AbstractMainActivity extends BaseActivity implements IMain public abstract NavigationView getNavigationView(); + public abstract ViewPager getViewPager(); + public abstract void showHideNavigation(boolean status); public abstract void handleShowHide(boolean isShow); public abstract AbstractMainFileShowFragment getMainFileShowFragment(); + + public abstract AbstractMainTitlebarFragment getMainTitlebarFragment(); + + public abstract AbstractMainFfmpegFragment getMainFfmpegFragment(); + + public abstract MainCompleteFragment getMainCompleteFragment(); + + public abstract AbstractMainHandleFragment getMainHandleFragment(); + + public abstract PathSelectFragment getCompletePathSelectFragment(); + + public abstract void refreshCompleteFileList(); } diff --git a/app/src/main/java/com/molihua/hlbmerge/activity/impl/AboutActivity.java b/app/src/main/java/com/molihua/hlbmerge/activity/impl/AboutActivity.java new file mode 100644 index 0000000..968a972 --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/activity/impl/AboutActivity.java @@ -0,0 +1,84 @@ +package com.molihua.hlbmerge.activity.impl; + +import android.content.Intent; +import android.widget.TextView; + +import com.blankj.molihuan.utilcode.util.AppUtils; +import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.activity.BaseActivity; +import com.molihua.hlbmerge.dialog.impl.StatementDialog; +import com.molihua.hlbmerge.fragment.impl.BackTitlebarFragment; +import com.molihua.hlbmerge.utils.FragmentTools; +import com.molihua.hlbmerge.utils.GeneralTools; +import com.molihua.hlbmerge.utils.LConstants; +import com.xuexiang.xui.widget.grouplist.XUIGroupListView; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * @ClassName: AboutActivity + * @Author: molihuan + * @Date: 2022/12/27/22:14 + * @Description: + */ +public class AboutActivity extends BaseActivity { + + private TextView mVersionTextView; + private XUIGroupListView mAboutGroupListView; + private TextView mCopyrightTextView; + + @Override + public int setContentViewID() { + return R.layout.activity_about; + } + + @Override + public void getComponents() { + mVersionTextView = findViewById(R.id.version); + mAboutGroupListView = findViewById(R.id.about_list); + mCopyrightTextView = findViewById(R.id.copyright); + } + + @Override + public void initData() { + + } + + @Override + public void initView() { + FragmentTools.fragmentReplace( + getSupportFragmentManager(), + R.id.titlebar_show_area, + new BackTitlebarFragment("关于我们"), + "about_back_titlebar" + ); + + mVersionTextView.setText(String.format("版本号:%s", AppUtils.getAppVersionName())); + + XUIGroupListView.newSection(this) + .addItemView(mAboutGroupListView.createItemView("用户协议"), v -> StatementDialog.showStatementDialog(this)) + .addItemView(mAboutGroupListView.createItemView("视频教程"), v -> { + GeneralTools.jumpBrowser(this, LConstants.URL_BILIBILI_HOMEPAGE); + }) + .addItemView(mAboutGroupListView.createItemView("开源许可"), v -> { + Intent intent = new Intent(this, HtmlActivity.class); + intent.putExtra("url", "file:///android_asset/openSourceLicense.html"); + intent.putExtra("title", "开源许可"); + startActivity(intent); + }) + .addItemView(mAboutGroupListView.createItemView("检查更新"), v -> GeneralTools.checkUpdata()) + .addTo(mAboutGroupListView); + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy", Locale.CHINA); + String currentYear = dateFormat.format(new Date()); + mCopyrightTextView.setText(String.format("© %1$s molihuan All rights reserved.", currentYear)); + + } + + @Override + public void setListeners() { + + } +} diff --git a/app/src/main/java/com/molihua/hlbmerge/activity/impl/HtmlActivity.java b/app/src/main/java/com/molihua/hlbmerge/activity/impl/HtmlActivity.java new file mode 100644 index 0000000..fcc068f --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/activity/impl/HtmlActivity.java @@ -0,0 +1,121 @@ +package com.molihua.hlbmerge.activity.impl; + +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.http.SslError; +import android.webkit.SslErrorHandler; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.activity.BaseActivity; +import com.molihua.hlbmerge.fragment.impl.BackTitlebarFragment; +import com.molihua.hlbmerge.utils.FragmentTools; + +/** + * @ClassName: HtmlActivity + * @Author: molihuan + * @Date: 2022/12/27/22:37 + * @Description: + */ +public class HtmlActivity extends BaseActivity { + + private WebView asstm; + private String title; + private String htmlUrl; + + @Override + public int setContentViewID() { + return R.layout.activity_html; + } + + @Override + public void getComponents() { + asstm = findViewById(R.id.asstm); + } + + @Override + public void initData() { + Intent intent = getIntent(); + htmlUrl = intent.getStringExtra("url"); + title = intent.getStringExtra("title"); + } + + @Override + public void initView() { + FragmentTools.fragmentReplace( + getSupportFragmentManager(), + R.id.titlebar_show_area, + new BackTitlebarFragment(title), + "html_back_titlebar" + ); + + //激活WebView为活跃状态,能正常执行网页的响应 + asstm.onResume(); + asstm.getSettings().setJavaScriptEnabled(true); + + asstm.loadUrl(htmlUrl); + asstm.setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + view.loadUrl(url); + return true; + } + + //开始载入页面时调用此方法,在这里我们可以设定一个loading的页面,告诉用户程序正在等待网络响应。 + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + //设定加载开始的操作 + } + + //在页面加载结束时调用。我们可以关闭loading 条,切换程序动作。 + @Override + public void onPageFinished(WebView view, String url) { + //设定加载结束的操作 + } + + //在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。 + @Override + public void onLoadResource(WebView view, String url) { + //设定加载资源的操作 + } + + @Override + //处理https请求 + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + handler.proceed(); //表示等待证书响应 + // handler.cancel(); //表示挂起连接,为默认方式 + // handler.handleMessage(null); //可做其他处理 + } + }); + //声明WebSettings子类 + WebSettings webSettings = asstm.getSettings(); + //如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript + webSettings.setJavaScriptEnabled(true); + //设置自适应屏幕,两者合用 + //webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小 + //webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小 + //缩放操作 + webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。 + webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放 + webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件 + //其他细节操作 + webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存 + webSettings.setAllowFileAccess(true); //设置可以访问文件 + webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口 + webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片 + webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式 + + } + + @Override + public void onBackPressed() { + if (asstm.canGoBack()) { + asstm.goBack(); + return; + } + + super.onBackPressed(); + } +} diff --git a/app/src/main/java/com/molihua/hlbmerge/activity/impl/MainActivity.java b/app/src/main/java/com/molihua/hlbmerge/activity/impl/MainActivity.java index 8079b53..97bc494 100644 --- a/app/src/main/java/com/molihua/hlbmerge/activity/impl/MainActivity.java +++ b/app/src/main/java/com/molihua/hlbmerge/activity/impl/MainActivity.java @@ -1,6 +1,8 @@ package com.molihua.hlbmerge.activity.impl; +import android.content.Intent; import android.view.MenuItem; +import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -11,9 +13,12 @@ import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.navigation.NavigationView; +import com.hjq.permissions.OnPermissionCallback; import com.molihua.hlbmerge.R; import com.molihua.hlbmerge.activity.AbstractMainActivity; import com.molihua.hlbmerge.adapter.CacheFileListAdapter; +import com.molihua.hlbmerge.dao.ConfigData; +import com.molihua.hlbmerge.dialog.impl.StatementDialog; import com.molihua.hlbmerge.entity.CacheFile; import com.molihua.hlbmerge.fragment.AbstractMainFfmpegFragment; import com.molihua.hlbmerge.fragment.AbstractMainFileShowFragment; @@ -26,10 +31,15 @@ import com.molihua.hlbmerge.fragment.impl.MainTitlebarFragment; import com.molihua.hlbmerge.service.ICacheFileManager; import com.molihua.hlbmerge.utils.FragmentTools; +import com.molihua.hlbmerge.utils.GeneralTools; import com.molihua.hlbmerge.utils.LConstants; +import com.molihuan.pathselector.fragment.impl.PathSelectFragment; +import com.molihuan.pathselector.utils.FileTools; import com.molihuan.pathselector.utils.Mtools; import com.molihuan.pathselector.utils.PermissionsTools; import com.xuexiang.xui.adapter.FragmentAdapter; +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction; +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog; import com.xuexiang.xui.widget.searchview.MaterialSearchView; import java.util.List; @@ -52,6 +62,8 @@ public class MainActivity extends AbstractMainActivity implements NavigationView @Override public int setContentViewID() { + //防止键盘的弹出将布局顶上去 + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); return R.layout.activity_main; } @@ -64,11 +76,47 @@ public void getComponents() { } + @Override public void initData() { - //申请权限 - PermissionsTools.getStoragePermissions(this); + //是否同意用户协议 + if (ConfigData.isAgreeTerms()) { + //友盟初始化 + + } else { + StatementDialog.showStatementDialog(this, new StatementDialog.IButtonCallback() { + @Override + public void onClick(MaterialDialog dialog, DialogAction which) { + + } + }); + } + + //存储权限的申请 + PermissionsTools.generalPermissionsOfStorage(this, new OnPermissionCallback() { + @Override + public void onGranted(@NonNull List<String> permissions, boolean all) { + boolean dataUseUri = FileTools.underAndroidDataUseUri(ConfigData.getCacheFilePath()); + if (!dataUseUri) { + //获取数据刷新列表 + updateCollectionFileList(); + refreshCacheFileList(); + } + } + }); + + PermissionsTools.specialPermissionsOfStorageWithDialog(this, true, new OnPermissionCallback() { + @Override + public void onGranted(@NonNull List<String> permissions, boolean all) { + boolean dataUseUri = FileTools.underAndroidDataUseUri(ConfigData.getCacheFilePath()); + if (!dataUseUri) { + //获取数据刷新列表 + updateCollectionFileList(); + refreshCacheFileList(); + } + } + }); mainTitlebarFragment = new MainTitlebarFragment(); mainFileShowFragment = new MainFileShowFragment(); @@ -81,12 +129,11 @@ public void initData() { @Override public void initView() { - //加载主显示区 FragmentAdapter<Fragment> adapter = new FragmentAdapter<>(getSupportFragmentManager()); adapter.addFragment(mainFileShowFragment, "主页"); adapter.addFragment(mainCompleteFragment, "完成文件"); - adapter.addFragment(mainFfmpegFragment, "ffmpeg"); + adapter.addFragment(mainFfmpegFragment, "工具"); viewPager.setOffscreenPageLimit(3); viewPager.setAdapter(adapter); @@ -99,7 +146,6 @@ public void initView() { true ); - } @Override @@ -118,6 +164,7 @@ public void setListeners() { */ @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { + Intent intent = null; int id = item.getItemId(); if (id == R.id.item_home) { viewPager.setCurrentItem(0, true); @@ -126,16 +173,22 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { } else if (id == R.id.item_ffmpeg) { viewPager.setCurrentItem(2, true); } else if (id == R.id.item_setting) { - + intent = new Intent(this, SettingsActivity.class); + startActivity(intent); } else if (id == R.id.item_teach) { - + GeneralTools.jumpBrowser(this, LConstants.URL_BILIBILI_HOMEPAGE); } else if (id == R.id.item_aboutus) { - + intent = new Intent(this, AboutActivity.class); + startActivity(intent); } else if (id == R.id.item_updatalog) { - + intent = new Intent(this, HtmlActivity.class); + intent.putExtra("url", "file:///android_asset/updataLog.html"); + intent.putExtra("title", "更新日志"); + startActivity(intent); } else if (id == R.id.item_exitapp) { - finish(); - System.exit(0); +// finish(); +// System.exit(0); + //EasyUpdate.checkUpdate(this, DEFAULT_UPDATE_URL); } //侧滑菜单关闭 drawerLayout.closeDrawers(); @@ -162,7 +215,6 @@ public void onBackPressed() { return; } - //按两次返回键退出程序 if (System.currentTimeMillis() - firstBackTime > 2000) { Mtools.toast("再按一次返回键退出程序"); @@ -174,6 +226,56 @@ public void onBackPressed() { } + @Override + public void handleShowHide(boolean isShow) { + //加载handle + FragmentTools.fragmentShowHide( + getSupportFragmentManager(), + R.id.frameLayout_main_handle_area, + mainHandleFragment, + LConstants.TAG_FRAGMENT_MAIN_HANDLE, + isShow + ); + } + + + @Override + public void onPageSelected(int position) { + + switch (position) { + case 0: + showHideImgView(true); + handleShowHide(isMultipleSelectionMode()); + break; + case 1: + showHideSearchView(false); + showTitleImgView(MainTitlebarFragment.ImgView.REFRESH); + handleShowHide(false); + break; + case 2: + showHideSearchView(false); + showHideImgView(false); + handleShowHide(false); + + break; + default: + } + + MenuItem item = bottomNavigView.getMenu().getItem(position); + setMainTitle(item.getTitle().toString()); + item.setChecked(true); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + @Override public BottomNavigationView getBottomNavigView() { return bottomNavigView; @@ -189,87 +291,79 @@ public NavigationView getNavigationView() { return navigationView; } + @Override + public ViewPager getViewPager() { + return viewPager; + } + @Override public AbstractMainFileShowFragment getMainFileShowFragment() { return mainFileShowFragment; } @Override - public void showHideNavigation(boolean status) { - if (status) { - drawerLayout.openDrawer(GravityCompat.START); - } else { - drawerLayout.close(); - } + public AbstractMainTitlebarFragment getMainTitlebarFragment() { + return mainTitlebarFragment; } @Override - public void handleShowHide(boolean isShow) { - //加载handle - FragmentTools.fragmentShowHide( - getSupportFragmentManager(), - R.id.frameLayout_main_handle_area, - mainHandleFragment, - LConstants.TAG_FRAGMENT_MAIN_HANDLE, - isShow - ); + public AbstractMainFfmpegFragment getMainFfmpegFragment() { + return mainFfmpegFragment; } @Override - public void setMainTitle(String text) { - mainTitlebarFragment.setMainTitle(text); + public MainCompleteFragment getMainCompleteFragment() { + return mainCompleteFragment; } @Override - public void showHideImgView(boolean status) { - mainTitlebarFragment.showHideImgView(status); + public AbstractMainHandleFragment getMainHandleFragment() { + return mainHandleFragment; } @Override - public void showHideSearchView(boolean status) { - mainTitlebarFragment.showHideSearchView(status); + public PathSelectFragment getCompletePathSelectFragment() { + return mainCompleteFragment.getPathSelectFragment(); } @Override - public MaterialSearchView getSearchView() { - return mainTitlebarFragment.getSearchView(); + public void refreshCompleteFileList() { + mainCompleteFragment.refreshFileList(); } - @Override - public void onPageSelected(int position) { - - switch (position) { - case 0: - showHideImgView(true); - handleShowHide(isMultipleSelectionMode()); - break; - case 1: - showHideSearchView(false); - showHideImgView(false); - handleShowHide(false); - break; - case 2: - showHideSearchView(false); - showHideImgView(false); - handleShowHide(false); - break; - default: + public void showHideNavigation(boolean status) { + if (status) { + drawerLayout.openDrawer(GravityCompat.START); + } else { + drawerLayout.close(); } + } - MenuItem item = bottomNavigView.getMenu().getItem(position); - setMainTitle(item.getTitle().toString()); - item.setChecked(true); + @Override + public void setMainTitle(String text) { + mainTitlebarFragment.setMainTitle(text); } @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + public void showHideImgView(boolean status) { + mainTitlebarFragment.showHideImgView(status); + } + @Override + public void showTitleImgView(MainTitlebarFragment.ImgView showImg) { + mainTitlebarFragment.showTitleImgView(showImg); } + @Override - public void onPageScrollStateChanged(int state) { + public void showHideSearchView(boolean status) { + mainTitlebarFragment.showHideSearchView(status); + } + @Override + public MaterialSearchView getSearchView() { + return mainTitlebarFragment.getSearchView(); } @Override diff --git a/app/src/main/java/com/molihua/hlbmerge/activity/impl/PlayVideoActivity.java b/app/src/main/java/com/molihua/hlbmerge/activity/impl/PlayVideoActivity.java new file mode 100644 index 0000000..cb44767 --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/activity/impl/PlayVideoActivity.java @@ -0,0 +1,151 @@ +package com.molihua.hlbmerge.activity.impl; + +import android.content.Intent; +import android.view.View; +import android.widget.Button; + +import com.blankj.molihuan.utilcode.util.ClipboardUtils; +import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.activity.BaseActivity; +import com.molihua.hlbmerge.controller.videocontroller.DKVideoController; +import com.molihuan.pathselector.utils.Mtools; + +import master.flame.danmaku.ui.widget.DanmakuView; +import xyz.doikki.videoplayer.player.VideoView; + +/** + * @ClassName: PlayVideoActivity + * @Author: molihuan + * @Date: 2022/12/28/0:48 + * @Description: + */ +public class PlayVideoActivity extends BaseActivity implements View.OnClickListener { + //复制路径按钮 + private Button btn_copypath; + //更新弹幕按钮 + private Button btn_updataxml; + + //DK播放器视图 + private VideoView videoView; + //DK控制器 + private DKVideoController videoController; + + //视频路径 + private String videoPath; + + public DanmakuView getDanmakuView() { + return videoController.getDanmakuView(); + } + + @Override + public int setContentViewID() { + return R.layout.activity_play_video; + } + + @Override + public void getComponents() { + btn_copypath = findViewById(R.id.btn_copypath); + btn_updataxml = findViewById(R.id.btn_updataxml); + videoView = findViewById(R.id.play_video_view); + } + + @Override + public void initData() { + Intent intent = getIntent(); + //获取播放视频的路径 + videoPath = intent.getStringExtra("videoPath"); + //全屏模式 + //ScreenUtils.setFullScreen(this); + //设置视频地址 + videoView.setUrl(videoPath); + //初始化视频控制器 + videoController = new DKVideoController(this, videoPath); + + //设置控制器 + videoView.setVideoController(videoController); + + //开始播放 + videoView.start(); + + } + + @Override + public void initView() { + btn_copypath.setText("路径:" + videoPath); + } + + @Override + public void setListeners() { + btn_copypath.setOnClickListener(this); + btn_updataxml.setOnClickListener(this); + } + + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_copypath: + ClipboardUtils.copyText(videoPath); + Mtools.toast("文件路径已复制到剪贴板"); + break; + case R.id.btn_updataxml: + break; + + } + } + + @Override + public void onBackPressed() { + //先让videoView处理返回事件 + if (videoController != null && videoController.onBackPressed()) { + return; + } + + super.onBackPressed(); + } + + @Override + public void onResume() { + super.onResume(); + //恢复播放 + if (videoView != null) { + videoView.resume(); + } + + getDanmakuView().resume(); + } + + @Override + public void onPause() { + super.onPause(); + //暂停视频 + if (videoView != null) { + videoView.pause(); + } + + getDanmakuView().pause(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + //销毁播放器 + releaseVideoViewDanmakuView(); + } + + /** + * 销毁播放器 + */ + protected void releaseVideoViewDanmakuView() { + if (videoView != null) { + videoView.release(); + videoView = null; + } + DanmakuView danmakuView = getDanmakuView(); + if (danmakuView != null) { + danmakuView.release(); + } + Mtools.log("释放videoView和danmakuView"); + } + +} diff --git a/app/src/main/java/com/molihua/hlbmerge/activity/impl/SettingsActivity.java b/app/src/main/java/com/molihua/hlbmerge/activity/impl/SettingsActivity.java new file mode 100644 index 0000000..40d90ea --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/activity/impl/SettingsActivity.java @@ -0,0 +1,216 @@ +package com.molihua.hlbmerge.activity.impl; + +import android.view.View; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.activity.BaseActivity; +import com.molihua.hlbmerge.dao.ConfigData; +import com.molihua.hlbmerge.fragment.impl.BackTitlebarFragment; +import com.molihua.hlbmerge.utils.FragmentTools; +import com.molihuan.pathselector.PathSelector; +import com.molihuan.pathselector.entity.FileBean; +import com.molihuan.pathselector.entity.FontBean; +import com.molihuan.pathselector.fragment.BasePathSelectFragment; +import com.molihuan.pathselector.listener.CommonItemListener; +import com.molihuan.pathselector.utils.MConstants; +import com.molihuan.pathselector.utils.Mtools; +import com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner; + +import java.util.List; + +/** + * @ClassName: SettingsActivity + * @Author: molihuan + * @Date: 2022/12/26/19:56 + * @Description: + */ +public class SettingsActivity extends BaseActivity implements View.OnClickListener, MaterialSpinner.OnItemSelectedListener { + private TextView cachePathShowTv; + private MaterialSpinner biliVersionMs; + private RelativeLayout customCachePathRela; + private TextView outputPathShowTv; + private RelativeLayout customOutputPathRela; + private LinearLayout biliVersionLine; + + + @Override + public int setContentViewID() { + return R.layout.activity_settings; + } + + @Override + public void getComponents() { + cachePathShowTv = findViewById(R.id.tv_cache_path_show); + biliVersionMs = findViewById(R.id.ms_bilibili_version); + customCachePathRela = findViewById(R.id.rela_custom_cache_path); + outputPathShowTv = findViewById(R.id.tv_output_path_show); + customOutputPathRela = findViewById(R.id.rela_custom_output_path); + biliVersionLine = findViewById(R.id.line_switch_bilibili_app_version); + } + + @Override + public void initData() { + + } + + @Override + public void initView() { + String cacheFilePath = ConfigData.getCacheFilePath(); + cachePathShowTv.setText(cacheFilePath); + outputPathShowTv.setText(ConfigData.getOutputFilePath()); + + if (cacheFilePath.equals(MConstants.PATH_ANRROID_DATA + ConfigData.TYPE_CACHE_FILE_PATH_INTERNAL)) { + biliVersionMs.setSelectedIndex(0); + customCachePathRela.setAlpha(0.2f); + biliVersionLine.setAlpha(1f); + } else if (cacheFilePath.equals(MConstants.PATH_ANRROID_DATA + ConfigData.TYPE_CACHE_FILE_PATH_ABROAD)) { + biliVersionMs.setSelectedIndex(1); + customCachePathRela.setAlpha(0.2f); + biliVersionLine.setAlpha(1f); + } else if (cacheFilePath.equals(MConstants.PATH_ANRROID_DATA + ConfigData.TYPE_CACHE_FILE_PATH_IPAD)) { + biliVersionMs.setSelectedIndex(2); + customCachePathRela.setAlpha(0.2f); + biliVersionLine.setAlpha(1f); + } else if (cacheFilePath.equals(MConstants.PATH_ANRROID_DATA + ConfigData.TYPE_CACHE_FILE_PATH_CONCEPT)) { + biliVersionMs.setSelectedIndex(3); + customCachePathRela.setAlpha(0.2f); + biliVersionLine.setAlpha(1f); + } else { + biliVersionMs.setSelectedIndex(4); + customCachePathRela.setAlpha(1f); + biliVersionLine.setAlpha(0.4f); + } + + FragmentTools.fragmentReplace( + getSupportFragmentManager(), + R.id.titlebar_show_area, + new BackTitlebarFragment("设置").setRightOption("恢复默认", new BackTitlebarFragment.IClickListener() { + @Override + public void onClick(View v) { + //默认缓存路径 + String cachePath = MConstants.PATH_ANRROID_DATA + ConfigData.TYPE_CACHE_FILE_PATH_INTERNAL; + ConfigData.setCacheFilePath(cachePath); + cachePathShowTv.setText(cachePath); + customCachePathRela.setAlpha(0.2f); + biliVersionMs.setSelectedIndex(0); + biliVersionLine.setAlpha(1f); + //默认输出路径 + String path = MConstants.DEFAULT_ROOTPATH + ConfigData.TYPE_OUTPUT_FILE_PATH_COMPLETE; + ConfigData.setOutputFilePath(path); + outputPathShowTv.setText(path); + + Mtools.toast("恢复默认成功"); + } + }), + "setting_back_titlebar" + ); + } + + @Override + public void setListeners() { + customCachePathRela.setOnClickListener(this); + customOutputPathRela.setOnClickListener(this); + biliVersionMs.setOnItemSelectedListener(this); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.rela_custom_cache_path) { + PathSelector.build(this, MConstants.BUILD_ACTIVITY) + .setRequestCode(3660) + .setTitlebarMainTitle(new FontBean("选择缓存文件夹(download)")) + .setAlwaysShowHandleFragment(true) + .setMaxCount(1) + .setHandleItemListeners( + new CommonItemListener("选择") { + @Override + public boolean onClick(View v, TextView tv, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) { + if (selectedFiles == null || selectedFiles.size() == 0) { + Mtools.toast("你还没有选择捏!请长按文件夹进行选择!"); + } else { + String path = selectedFiles.get(0).getPath(); + boolean success = ConfigData.setCacheFilePath(path); + cachePathShowTv.setText(path); + biliVersionMs.setSelectedIndex(4); + customCachePathRela.setAlpha(1f); + biliVersionLine.setAlpha(0.4f); + + if (success) { + Mtools.toast("设置成功"); + } else { + Mtools.toast("设置失败"); + } + + } + return false; + } + } + ) + .show(); + } else if (id == R.id.rela_custom_output_path) { + PathSelector.build(this, MConstants.BUILD_ACTIVITY) + .setRequestCode(3660) + .setTitlebarMainTitle(new FontBean("选择输出路径")) + .setAlwaysShowHandleFragment(true) + .setMaxCount(1) + .setHandleItemListeners( + new CommonItemListener("选择") { + @Override + public boolean onClick(View v, TextView tv, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) { + if (selectedFiles == null || selectedFiles.size() == 0) { + Mtools.toast("你还没有选择捏!请长按文件夹进行选择!"); + } else { + String path = selectedFiles.get(0).getPath(); + boolean success = ConfigData.setOutputFilePath(path); + outputPathShowTv.setText(path); + if (success) { + Mtools.toast("设置成功"); + } else { + Mtools.toast("设置失败"); + } + } + return false; + } + } + ) + .show(); + } + } + + @Override + public void onItemSelected(MaterialSpinner v, int position, long id, Object item) { + int msid = v.getId(); + if (msid == R.id.ms_bilibili_version) { + String cachePath; + switch (position) { + case 0: + cachePath = MConstants.PATH_ANRROID_DATA + ConfigData.TYPE_CACHE_FILE_PATH_INTERNAL; + break; + case 1: + cachePath = MConstants.PATH_ANRROID_DATA + ConfigData.TYPE_CACHE_FILE_PATH_ABROAD; + break; + case 2: + cachePath = MConstants.PATH_ANRROID_DATA + ConfigData.TYPE_CACHE_FILE_PATH_IPAD; + break; + case 3: + cachePath = MConstants.PATH_ANRROID_DATA + ConfigData.TYPE_CACHE_FILE_PATH_CONCEPT; + break; + case 4://空白什么都不做 + return; + default: + cachePath = MConstants.PATH_ANRROID_DATA + ConfigData.TYPE_CACHE_FILE_PATH_INTERNAL; + } + + ConfigData.setCacheFilePath(cachePath); + cachePathShowTv.setText(cachePath); + customCachePathRela.setAlpha(0.2f); + biliVersionLine.setAlpha(1f); + + } + + } +} diff --git a/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/DKVideoController.java b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/DKVideoController.java new file mode 100644 index 0000000..10ce8b5 --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/DKVideoController.java @@ -0,0 +1,298 @@ +package com.molihua.hlbmerge.controller.videocontroller; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.blankj.molihuan.utilcode.util.FileUtils; +import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.controller.videocontroller.component.VideoBottomControlView; +import com.molihua.hlbmerge.controller.videocontroller.component.VideoCompleteView; +import com.molihua.hlbmerge.controller.videocontroller.component.VideoDanmakuView; +import com.molihua.hlbmerge.controller.videocontroller.component.VideoGestureView; +import com.molihua.hlbmerge.controller.videocontroller.component.VideoSettingView; +import com.molihua.hlbmerge.controller.videocontroller.component.VideoTitleView; +import com.molihuan.pathselector.utils.Mtools; + +import master.flame.danmaku.danmaku.model.android.DanmakuContext; +import master.flame.danmaku.ui.widget.DanmakuView; +import xyz.doikki.videocontroller.component.ErrorView; +import xyz.doikki.videocontroller.component.PrepareView; +import xyz.doikki.videoplayer.controller.ControlWrapper; +import xyz.doikki.videoplayer.controller.GestureVideoController; +import xyz.doikki.videoplayer.controller.IControlComponent; +import xyz.doikki.videoplayer.player.VideoView; +import xyz.doikki.videoplayer.util.PlayerUtils; + +/** + * @ClassName: DKVideoController + * @Author: molihuan + * @Date: 2022/12/29/22:03 + * @Description: + */ +public class DKVideoController extends GestureVideoController implements View.OnClickListener, IControlComponent { + //视频路径 + private String videoPath; + //上下文 + private Context mContext; + //上下文 + private Activity mActivity; + + //弹幕视图 + private DanmakuView mDanmakuView; + + + //屏幕锁按钮 + private ImageView lockImgView; + + private VideoDanmakuView videoDanmakuView; + + private VideoSettingView videoSettingView; + + public DanmakuView getDanmakuView() { + return videoDanmakuView.getDanmakuView(); + } + + public DanmakuContext getDanmakuContext() { + return videoDanmakuView.getDanmakuContext(); + } + + public VideoSettingView getVideoSettingView() { + return videoSettingView; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_dk_video_controller; + } + + public DKVideoController(@NonNull Context context, String videoPath) { + super(context); + //获取组件 + getComponents(); + //初始化数据 + initData(context, videoPath); + //初始化视图 + initControlView(); + //设置监听 + setListeners(); + } + + private void getComponents() { + lockImgView = findViewById(R.id.iv_lock); + } + + private void initData(Context context, String videoPath) { + this.videoPath = videoPath; + mContext = context; + mActivity = (Activity) context; + } + + private void initControlView() { + //添加基础视图 + videoDanmakuView = new VideoDanmakuView(mContext, videoPath, this); + //获取弹幕视图 + mDanmakuView = getDanmakuView(); + + VideoBottomControlView bottomControlView = new VideoBottomControlView(mContext, this); + VideoCompleteView videoCompleteView = new VideoCompleteView(mContext, mDanmakuView); + ErrorView errorView = new ErrorView(mContext); + PrepareView prepareView = new PrepareView(mContext); + VideoTitleView videoTitleView = new VideoTitleView(mContext, FileUtils.getFileNameNoExtension(videoPath), this); + VideoGestureView gestureView = new VideoGestureView(mContext, mDanmakuView); + videoSettingView = new VideoSettingView(mContext, this); + + addControlComponent( + videoTitleView, + videoSettingView, + bottomControlView, + videoCompleteView, + errorView, + prepareView, + gestureView, + videoDanmakuView + ); + + //是否在竖屏模式下开始手势控制 + setEnableInNormal(true); + //设置播放视图自动隐藏超时 + setDismissTimeout(4000); + //显示控制器 + show(); + } + + + private void setListeners() { + lockImgView.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.iv_lock) { + mControlWrapper.toggleLockState(); + } + } + + + /** + * 处理返回事件,需要Activity将返回事件传递过来 + * + * @return 已经处理了就返回true, 没有处理返回false + */ + @Override + public boolean onBackPressed() { + + if (isLocked()) { + show(); + Mtools.toast("请先解锁屏幕"); + return true; + } + + if (videoSettingView.isExpandableLayout()) { + videoSettingView.changeExpandableLayout(false); + return true; + } + + if (mControlWrapper.isFullScreen()) { + mControlWrapper.toggleFullScreenByVideoSize(mActivity); + return true; + } + + mActivity.finish(); + + return false; + } + + @Override + public void attach(@NonNull ControlWrapper controlWrapper) { +// mControlWrapper = controlWrapper; + } + + @Nullable + @Override + public View getView() { + return this; + } + + /** + * 监听控制视图的显示和隐藏,在这里隐藏控和显示制视图 + * show()和 hide()调用会回调此方法 + * + * @param isVisible + * @param anim 动画 + */ + @Override + public void onVisibilityChanged(boolean isVisible, Animation anim) { + if (mControlWrapper == null || mControlWrapper.isFullScreen()) { + if (isVisible) { + if (lockImgView.getVisibility() == GONE) { + lockImgView.setVisibility(VISIBLE); + if (anim != null) { + lockImgView.startAnimation(anim); + } + } + } else { + lockImgView.setVisibility(GONE); + if (anim != null) { + lockImgView.startAnimation(anim); + } + } + } + + } + + + @Override + public void onPlayStateChanged(int playState) { + super.onPlayStateChanged(playState); + switch (playState) { + //调用release方法会回到此状态 + case VideoView.STATE_IDLE: + lockImgView.setSelected(false); + break; + case VideoView.STATE_PLAYING: + case VideoView.STATE_PAUSED: + case VideoView.STATE_PREPARED: + case VideoView.STATE_ERROR: + case VideoView.STATE_BUFFERED: + break; + case VideoView.STATE_PREPARING: + case VideoView.STATE_BUFFERING: + break; + case VideoView.STATE_PLAYBACK_COMPLETED: + lockImgView.setVisibility(GONE); + lockImgView.setSelected(false); + break; + } + } + + + @Override + public void onPlayerStateChanged(int playerState) { + super.onPlayerStateChanged(playerState); + switch (playerState) { + case VideoView.PLAYER_NORMAL: + setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + lockImgView.setVisibility(GONE); + break; + case VideoView.PLAYER_FULL_SCREEN: + if (isShowing()) { + lockImgView.setVisibility(VISIBLE); + } else { + lockImgView.setVisibility(GONE); + } + break; + } + + if (mActivity != null && hasCutout()) { + int orientation = mActivity.getRequestedOrientation(); + int dp24 = PlayerUtils.dp2px(getContext(), 24); + int cutoutHeight = getCutoutHeight(); + if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { + FrameLayout.LayoutParams lblp = (LayoutParams) lockImgView.getLayoutParams(); + lblp.setMargins(dp24, 0, dp24, 0); + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { + FrameLayout.LayoutParams layoutParams = (LayoutParams) lockImgView.getLayoutParams(); + layoutParams.setMargins(dp24 + cutoutHeight, 0, dp24 + cutoutHeight, 0); + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { + FrameLayout.LayoutParams layoutParams = (LayoutParams) lockImgView.getLayoutParams(); + layoutParams.setMargins(dp24, 0, dp24, 0); + } + } + } + + + @Override + public void setProgress(int duration, int position) { + + } + + /** + * 锁定状态发生改变监听 + * + * @param isLocked + */ + @Override + public void onLockStateChanged(boolean isLocked) { + if (isLocked) { + lockImgView.setSelected(true); + Mtools.toast("已锁定"); + } else { + lockImgView.setSelected(false); + Mtools.toast("已解锁"); + } + } + + +} diff --git a/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoBottomControlView.java b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoBottomControlView.java new file mode 100644 index 0000000..872d19f --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoBottomControlView.java @@ -0,0 +1,388 @@ +package com.molihua.hlbmerge.controller.videocontroller.component; + +import static xyz.doikki.videoplayer.util.PlayerUtils.stringForTime; + +import android.app.Activity; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.animation.Animation; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.controller.videocontroller.DKVideoController; +import com.molihua.hlbmerge.dao.ConfigData; +import com.molihuan.pathselector.utils.Mtools; + +import master.flame.danmaku.ui.widget.DanmakuView; +import xyz.doikki.videoplayer.controller.ControlWrapper; +import xyz.doikki.videoplayer.controller.IGestureComponent; +import xyz.doikki.videoplayer.player.VideoView; + +/** + * @ClassName: VideoBottomControlView + * @Author: molihuan + * @Date: 2023/01/01/22:07 + * @Description: 底部控制面板 + */ +public class VideoBottomControlView extends FrameLayout implements IGestureComponent, View.OnClickListener, SeekBar.OnSeekBarChangeListener { + + private ControlWrapper mControlWrapper; + //上下文 + private Context mContext; + //上下文 + private Activity mActivity; + + //底部控制总布局 + private RelativeLayout bottomControlRela; + //播放按钮 + private ImageView playImgView; + //弹幕按钮 + private ImageView barrageImgView; + //当前播放时间 + private TextView currTimeTv; + //总时间 + private TextView totalTimeTv; + //进度条 + private SeekBar progressSb; + //全屏按钮 + private ImageView fullScreenImgView; + + //弹幕视图 + private DanmakuView danmakuView; + + //视频是否已经播放完 + private boolean playCompleted = false; + + + public VideoBottomControlView(@NonNull Context context, DKVideoController dkVideoController) { + super(context); + //获取组件 + getComponents(); + //初始化数据 + initData(context, dkVideoController); + //初始化视图 + initMyView(); + //设置监听 + setListeners(); + } + + private void getComponents() { + + LayoutInflater.from(getContext()).inflate(R.layout.fragment_dk_video_bottom_control, this, true); + + bottomControlRela = findViewById(R.id.rela_bottom_control_panel); + playImgView = findViewById(R.id.iv_play); + barrageImgView = findViewById(R.id.iv_barrage); + currTimeTv = findViewById(R.id.curr_time); + totalTimeTv = findViewById(R.id.total_time); + progressSb = findViewById(R.id.sb_progress); + fullScreenImgView = findViewById(R.id.iv_full_screen); + } + + private void initData(Context context, DKVideoController dkVideoController) { + danmakuView = dkVideoController.getDanmakuView(); + mContext = context; + mActivity = (Activity) context; + } + + private void initMyView() { + //获取弹幕是否显示配置 + if (!ConfigData.isOpenBarrage()) { + barrageImgSrc(false); + } + } + + private void setListeners() { + playImgView.setOnClickListener(this); + barrageImgView.setOnClickListener(this); + progressSb.setOnSeekBarChangeListener(this); + fullScreenImgView.setOnClickListener(this); + } + + @Override + public void onStartSlide() { + + } + + @Override + public void onStopSlide() { + + } + + @Override + public void onPositionChange(int slidePosition, int currentPosition, int duration) { + + } + + @Override + public void onBrightnessChange(int percent) { + + } + + @Override + public void onVolumeChange(int percent) { + + } + + @Override + public void attach(@NonNull ControlWrapper controlWrapper) { + mControlWrapper = controlWrapper; + } + + @Nullable + @Override + public View getView() { + return this; + } + + /** + * 监听控制视图的显示和隐藏,在这里隐藏控和显示制视图 + * show()和 hide()调用会回调此方法 + * + * @param isVisible + * @param anim 动画 + */ + @Override + public void onVisibilityChanged(boolean isVisible, Animation anim) { + if (isVisible) { + bottomControlRela.setVisibility(View.VISIBLE); + } else { + bottomControlRela.setVisibility(View.GONE); + } + //设置动画 + if (anim != null) { + bottomControlRela.startAnimation(anim); + } + } + + /** + * 下面是改变按钮图片的方法 + * + * @param status + */ + public void playImgSrc(boolean status) { + if (status) { + playImgView.setImageResource(R.drawable.ic_video_play); + } else { + playImgView.setImageResource(R.drawable.ic_video_pause); + } + } + + public void barrageImgSrc(boolean status) { + if (status) { + barrageImgView.setImageResource(R.drawable.ic_barrage_open); + } else { + barrageImgView.setImageResource(R.drawable.ic_barrage_close); + } + } + + public void fullScreenImgSrc(boolean status) { + if (status) { + fullScreenImgView.setImageResource(R.drawable.ic_video_view_biggest); + } else { + fullScreenImgView.setImageResource(R.drawable.ic_video_view_minimum); + } + } + + /** + * 播放状态改变监听 + * + * @param playState + */ + @Override + public void onPlayStateChanged(int playState) { + switch (playState) { + case VideoView.STATE_PREPARING://准备中 + Mtools.log("准备中--------视频"); + break; + case VideoView.STATE_PREPARED://准备完成 + Mtools.log("准备完成--------视频"); + break; + case VideoView.STATE_PLAYING://播放中 + Mtools.log("播放中--------视频"); + playCompleted = false; + //必须调用才可开心刷新进度回调 + mControlWrapper.startProgress(); + playImgSrc(true); + + if (danmakuView.isPaused()) { + danmakuView.resume(); + } + + break; + case VideoView.STATE_PAUSED://暂停 + Mtools.log("暂停--------视频"); + mControlWrapper.stopProgress(); + playImgSrc(false); + + danmakuView.pause(); + break; + case VideoView.STATE_PLAYBACK_COMPLETED://播放完成 + Mtools.log("播放完成--------视频"); + playCompleted = true; + //显示控制视图 + mControlWrapper.show(); + playImgSrc(false); + danmakuView.hideAndPauseDrawTask(); + + progressSb.setProgress(0); + progressSb.setSecondaryProgress(0); + break; + //调用release方法会回到此状态 + case VideoView.STATE_IDLE://闲置 + Mtools.log("闲置--------视频"); + setVisibility(GONE); + progressSb.setProgress(0); + progressSb.setSecondaryProgress(0); + break; + //下面的回调不常用 + case VideoView.STATE_BUFFERING://缓冲中 + Mtools.log("缓冲中--------视频"); + break; + case VideoView.STATE_BUFFERED://缓冲完成 + Mtools.log("缓冲完成--------视频"); + break; + case VideoView.STATE_START_ABORT://开始、播放、中止 + Mtools.log("开始、播放、中止--------视频"); + break; + case VideoView.STATE_ERROR://错误 + Mtools.log("错误--------视频"); + break; + + } + } + + /** + * 普通、全屏、小屏播放器状态监听 + * + * @param playerState + */ + @Override + public void onPlayerStateChanged(int playerState) { + switch (playerState) { + case VideoView.PLAYER_NORMAL://普通 + Mtools.log("普通--------播放器"); + fullScreenImgSrc(true); + break; + case VideoView.PLAYER_TINY_SCREEN://小屏 + Mtools.log("小屏--------播放器"); + fullScreenImgSrc(true); + break; + case VideoView.PLAYER_FULL_SCREEN://全屏 + Mtools.log("全屏--------播放器"); + fullScreenImgSrc(false); + break; + } + } + + /** + * 播放进度回调 + * 只有调用了startProgress()才会开始回调此方法 + * + * @param duration 视频总时长 + * @param position 视频当前时长 + */ + @Override + public void setProgress(int duration, int position) { + + if (duration > 0) { + progressSb.setEnabled(true); + int pos = (int) (position * 1.0 / duration * progressSb.getMax()); + progressSb.setProgress(pos); + } else { + progressSb.setEnabled(false); + } + + int percent = mControlWrapper.getBufferedPercentage(); + if (percent >= 95) { //解决缓冲进度不能100%问题 + progressSb.setSecondaryProgress(progressSb.getMax()); + } else { + progressSb.setSecondaryProgress(percent * 10); + } + + currTimeTv.setText(stringForTime(position)); + totalTimeTv.setText(stringForTime(duration)); + + } + + @Override + public void onLockStateChanged(boolean isLocked) { + onVisibilityChanged(!isLocked, null); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.iv_play) { + if (playCompleted) { + mControlWrapper.replay(true); + //danmakuView.restart(); + danmakuView.show(); + danmakuView.seekTo((long) 0); + } else { + mControlWrapper.togglePlay(); + danmakuView.toggle(); + } + + } else if (id == R.id.iv_barrage) { + if (danmakuView.isShown()) { + danmakuView.hide(); + barrageImgSrc(false); + ConfigData.setOpenBarrage(false); + } else { + danmakuView.show(); + barrageImgSrc(true); + ConfigData.setOpenBarrage(true); + } + } else if (id == R.id.iv_full_screen) { + mControlWrapper.toggleFullScreenByVideoSize(mActivity); + } + } + + /** + * 进度条组件进度改变监听 + * + * @param seekBar + * @param progress + * @param fromUser + */ + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (!fromUser) { + return; + } + //获取进度 + long duration = mControlWrapper.getDuration(); + long newPosition = (duration * progress) / progressSb.getMax(); + if (!danmakuView.isPaused()) { + currTimeTv.setText(stringForTime((int) newPosition)); + totalTimeTv.setText(stringForTime((int) duration)); + } + danmakuView.seekTo(newPosition); + mControlWrapper.seekTo((int) newPosition); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + mControlWrapper.stopProgress(); + mControlWrapper.stopFadeOut(); + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + long duration = mControlWrapper.getDuration(); + long newPosition = (duration * seekBar.getProgress()) / progressSb.getMax(); + mControlWrapper.seekTo((int) newPosition); + danmakuView.seekTo(newPosition); + mControlWrapper.startProgress(); + mControlWrapper.startFadeOut(); + } +} diff --git a/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoCompleteView.java b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoCompleteView.java new file mode 100644 index 0000000..4c30514 --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoCompleteView.java @@ -0,0 +1,92 @@ +package com.molihua.hlbmerge.controller.videocontroller.component; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.animation.Animation; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.molihua.hlbmerge.R; + +import master.flame.danmaku.ui.widget.DanmakuView; +import xyz.doikki.videoplayer.controller.ControlWrapper; +import xyz.doikki.videoplayer.controller.IControlComponent; +import xyz.doikki.videoplayer.player.VideoView; + +/** + * @ClassName: VideoCompleteView + * @Author: molihuan + * @Date: 2023/01/01/13:45 + * @Description: 视频播放完成View + */ +public class VideoCompleteView extends FrameLayout implements IControlComponent { + + protected DanmakuView danmakuView; + + private ControlWrapper mControlWrapper; + + + public VideoCompleteView(@NonNull Context context, DanmakuView danmakuView) { + super(context); + this.danmakuView = danmakuView; + } + + { + setVisibility(GONE); + LayoutInflater.from(getContext()).inflate(R.layout.fragment_dk_video_complete, this, true); + findViewById(R.id.iv_replay).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mControlWrapper.replay(true); + danmakuView.show(); + danmakuView.seekTo((long) 0); + } + }); +// setClickable(true); + } + + + @Override + public void attach(@NonNull ControlWrapper controlWrapper) { + mControlWrapper = controlWrapper; + } + + @Nullable + @Override + public View getView() { + return this; + } + + @Override + public void onVisibilityChanged(boolean isVisible, Animation anim) { + + } + + @Override + public void onPlayStateChanged(int playState) { + if (playState == VideoView.STATE_PLAYBACK_COMPLETED) { + setVisibility(VISIBLE); + bringToFront(); + } else { + setVisibility(GONE); + } + } + + @Override + public void onPlayerStateChanged(int playerState) { + + } + + @Override + public void setProgress(int duration, int position) { + + } + + @Override + public void onLockStateChanged(boolean isLocked) { + + } +} diff --git a/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoDanmakuView.java b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoDanmakuView.java new file mode 100644 index 0000000..3f9b443 --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoDanmakuView.java @@ -0,0 +1,229 @@ +package com.molihua.hlbmerge.controller.videocontroller.component; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.animation.Animation; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.blankj.molihuan.utilcode.util.FileUtils; +import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.controller.videocontroller.DKVideoController; +import com.molihua.hlbmerge.dao.ConfigData; +import com.molihua.hlbmerge.utils.BiliDanmukuParserTools; +import com.molihuan.pathselector.utils.Mtools; + +import master.flame.danmaku.controller.DrawHandler; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.DanmakuTimer; +import master.flame.danmaku.danmaku.model.android.DanmakuContext; +import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; +import master.flame.danmaku.danmaku.parser.IDataSource; +import master.flame.danmaku.danmaku.parser.android.AndroidFileSource; +import master.flame.danmaku.ui.widget.DanmakuView; +import xyz.doikki.videoplayer.controller.ControlWrapper; +import xyz.doikki.videoplayer.controller.IGestureComponent; + +/** + * @ClassName: VideoDanmakuView + * @Author: molihuan + * @Date: 2023/01/01/21:30 + * @Description: + */ +public class VideoDanmakuView extends FrameLayout implements IGestureComponent { + //弹幕视图 + private DanmakuView danmakuView; + + //弹幕内容 + private DanmakuContext danmakuContext; + + private ControlWrapper mControlWrapper; + private String videoPath; + private Context mContext; + private DKVideoController dkVideoController; + + public DanmakuView getDanmakuView() { + return danmakuView; + } + + public DanmakuContext getDanmakuContext() { + return danmakuContext; + } + + public VideoDanmakuView(@NonNull Context context, String videoPath, DKVideoController dkVideoController) { + super(context); + //获取组件 + getComponents(); + //初始化数据 + initData(context, videoPath, dkVideoController); + //初始化视图 + initMyView(); + //设置监听 + setListeners(); + } + + private void getComponents() { + LayoutInflater.from(getContext()).inflate(R.layout.fragment_dk_video_danmaku, this, true); + danmakuView = findViewById(R.id.danmaku_show_area); + } + + private void initData(Context context, String videoPath, DKVideoController dkVideoController) { + this.videoPath = videoPath; + mContext = context; + this.dkVideoController = dkVideoController; + } + + private void initMyView() { + //初始化弹幕 + initDanmakuView(); + } + + /** + * 初始化弹幕 + */ + private void initDanmakuView() { + //获取弹幕是否显示配置 + if (!ConfigData.isOpenBarrage()) { + danmakuView.hide(); + } + //初始化弹幕配置 + initDanmakuConfig(); + + if (danmakuView != null) { + String localXmlPath = videoPath.replace(FileUtils.getFileExtension(videoPath), "xml"); + if (!FileUtils.isFileExists(localXmlPath)) { + return; + } + //String localXmlPath="https://comment.bilibili.com/367013145.xml"; + IDataSource danmakuFileSource = new AndroidFileSource(localXmlPath); + //自定义弹幕解析工具 + BaseDanmakuParser danmakuParser = new BiliDanmukuParserTools(mContext); + danmakuParser.load(danmakuFileSource); + //设置弹幕绘制回调 + danmakuView.setCallback(new DrawHandler.Callback() { + @Override + public void prepared() { + Mtools.log("弹幕准备完成--------弹幕"); + danmakuView.start(); + } + + @Override + public void updateTimer(DanmakuTimer timer) { + + } + + @Override + public void danmakuShown(BaseDanmaku danmaku) { + + } + + @Override + public void drawingFinished() { + Mtools.log("弹幕全部绘制完成--------弹幕"); + //先暂停如果停止了就启动不了弹幕了 + danmakuView.hideAndPauseDrawTask(); + } + }); + + //进行弹幕准备 + danmakuView.prepare(danmakuParser, danmakuContext); + //显示FPS + //danmakuView.showFPS(true); + //开启绘制缓存 + danmakuView.enableDanmakuDrawingCache(true); + } + + } + + private void initDanmakuConfig() { + //创建弹幕配置 + danmakuContext = DanmakuContext.create(); + +// 滚动弹幕最大显示5行 +// HashMap<Integer, Integer> maxLinesPair = new HashMap<>(); +// maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 5); +// danmakuContext.setMaximumLines(maxLinesPair)//设置滚动弹幕最大显示行 + + //默认值应该从配置中读取 + int danmakuSize = ConfigData.getDanmakuSize(); + int danmakuAlpha = ConfigData.getDanmakuAlpha(); + int danmakuSpeed = ConfigData.getDanmakuSpeed(); + + //设置基本弹幕配置 + danmakuContext + .setSpecialDanmakuVisibility(true)//显示特殊弹幕 + .setScaleTextSize((float) danmakuSize / 100)//设置弹幕大小 + .setDanmakuTransparency((float) danmakuAlpha / 100)//设置透明度 + .setScrollSpeedFactor((float) (200 - danmakuSpeed) / 100)//设置弹幕滚动速度系数 + ; + + + } + + private void setListeners() { + } + + @Override + public void onStartSlide() { + + } + + @Override + public void onStopSlide() { + + } + + @Override + public void onPositionChange(int slidePosition, int currentPosition, int duration) { + + } + + @Override + public void onBrightnessChange(int percent) { + + } + + @Override + public void onVolumeChange(int percent) { + + } + + @Override + public void attach(@NonNull ControlWrapper controlWrapper) { + mControlWrapper = controlWrapper; + } + + @Nullable + @Override + public View getView() { + return this; + } + + @Override + public void onVisibilityChanged(boolean isVisible, Animation anim) { + + } + + @Override + public void onPlayStateChanged(int playState) { + + } + + @Override + public void onPlayerStateChanged(int playerState) { + + } + + @Override + public void setProgress(int duration, int position) { + + } + + @Override + public void onLockStateChanged(boolean isLocked) { + + } +} diff --git a/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoGestureView.java b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoGestureView.java new file mode 100644 index 0000000..0ae4928 --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoGestureView.java @@ -0,0 +1,160 @@ +package com.molihua.hlbmerge.controller.videocontroller.component; + +import static xyz.doikki.videoplayer.util.PlayerUtils.stringForTime; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.animation.Animation; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import master.flame.danmaku.ui.widget.DanmakuView; +import xyz.doikki.videocontroller.R; +import xyz.doikki.videoplayer.controller.ControlWrapper; +import xyz.doikki.videoplayer.controller.IGestureComponent; +import xyz.doikki.videoplayer.player.VideoView; + +/** + * @ClassName: VideoGestureView + * @Author: molihuan + * @Date: 2023/01/01/14:36 + * @Description: 视频页面手势 + */ +public class VideoGestureView extends FrameLayout implements IGestureComponent { + + protected DanmakuView danmakuView; + + private ControlWrapper mControlWrapper; + + private final ImageView mIcon; + private final ProgressBar mProgressPercent; + private final TextView mTextPercent; + + private final LinearLayout mCenterContainer; + + private long newPosition; + + public VideoGestureView(@NonNull Context context, DanmakuView danmakuView) { + super(context); + this.danmakuView = danmakuView; + } + + { + setVisibility(GONE); + LayoutInflater.from(getContext()).inflate(R.layout.dkplayer_layout_gesture_control_view, this, true); + mIcon = findViewById(R.id.iv_icon); + mProgressPercent = findViewById(R.id.pro_percent); + mTextPercent = findViewById(R.id.tv_percent); + mCenterContainer = findViewById(R.id.center_container); + } + + @Override + public void attach(@NonNull ControlWrapper controlWrapper) { + mControlWrapper = controlWrapper; + } + + @Override + public View getView() { + return this; + } + + @Override + public void onVisibilityChanged(boolean isVisible, Animation anim) { + + } + + @Override + public void onPlayerStateChanged(int playerState) { + + } + + @Override + public void onStartSlide() { + mControlWrapper.hide(); + mCenterContainer.setVisibility(VISIBLE); + mCenterContainer.setAlpha(1f); + } + + @Override + public void onStopSlide() { + mCenterContainer.animate() + .alpha(0f) + .setDuration(300) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mCenterContainer.setVisibility(GONE); + } + }) + .start(); + + + } + + @Override + public void onPositionChange(int slidePosition, int currentPosition, int duration) { + mProgressPercent.setVisibility(GONE); + if (slidePosition > currentPosition) { + mIcon.setImageResource(R.drawable.dkplayer_ic_action_fast_forward); + } else { + mIcon.setImageResource(R.drawable.dkplayer_ic_action_fast_rewind); + } + mTextPercent.setText(String.format("%s/%s", stringForTime(slidePosition), stringForTime(duration))); + newPosition = slidePosition; + danmakuView.seekTo(newPosition); + } + + @Override + public void onBrightnessChange(int percent) { + mProgressPercent.setVisibility(VISIBLE); + mIcon.setImageResource(R.drawable.dkplayer_ic_action_brightness); + mTextPercent.setText(percent + "%"); + mProgressPercent.setProgress(percent); + } + + @Override + public void onVolumeChange(int percent) { + + mProgressPercent.setVisibility(VISIBLE); + if (percent <= 0) { + mIcon.setImageResource(R.drawable.dkplayer_ic_action_volume_off); + } else { + mIcon.setImageResource(R.drawable.dkplayer_ic_action_volume_up); + } + mTextPercent.setText(percent + "%"); + mProgressPercent.setProgress(percent); + } + + @Override + public void onPlayStateChanged(int playState) { + if (playState == VideoView.STATE_IDLE + || playState == VideoView.STATE_START_ABORT + || playState == VideoView.STATE_PREPARING + || playState == VideoView.STATE_PREPARED + || playState == VideoView.STATE_ERROR + || playState == VideoView.STATE_PLAYBACK_COMPLETED) { + setVisibility(GONE); + } else { + setVisibility(VISIBLE); + } + } + + @Override + public void setProgress(int duration, int position) { + + } + + @Override + public void onLockStateChanged(boolean isLock) { + + } +} diff --git a/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoSettingView.java b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoSettingView.java new file mode 100644 index 0000000..9a4dbc0 --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoSettingView.java @@ -0,0 +1,249 @@ +package com.molihua.hlbmerge.controller.videocontroller.component; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.animation.Animation; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.controller.videocontroller.DKVideoController; +import com.molihua.hlbmerge.dao.ConfigData; +import com.xuexiang.xui.widget.layout.ExpandableLayout; +import com.xuexiang.xui.widget.picker.XSeekBar; + +import master.flame.danmaku.danmaku.model.android.DanmakuContext; +import xyz.doikki.videoplayer.controller.ControlWrapper; +import xyz.doikki.videoplayer.controller.IGestureComponent; + +/** + * @ClassName: VideoSettingView + * @Author: molihuan + * @Date: 2023/01/02/13:44 + * @Description: + */ +public class VideoSettingView extends FrameLayout implements IGestureComponent, XSeekBar.OnSeekBarListener, View.OnClickListener { + + private ControlWrapper mControlWrapper; + private Context mContext; + private DKVideoController dkVideoController; + + //弹幕内容 + private DanmakuContext danmakuContext; + + private ExpandableLayout expandableLayout; + + private XSeekBar danmakuSizeXSB; + private XSeekBar danmakuAlphaXSB; + private XSeekBar danmakuSpeedXSB; + private XSeekBar danmakuShowAreaXSB; + + private int danmakuSize; + private int danmakuAlpha; + private int danmakuSpeed; + + private View emptyView; + + public VideoSettingView(@NonNull Context context, DKVideoController dkVideoController) { + super(context); + //获取组件 + getComponents(); + //初始化数据 + initData(context, dkVideoController); + //初始化视图 + initMyView(); + //设置监听 + setListeners(); + } + + private void getComponents() { + LayoutInflater.from(getContext()).inflate(R.layout.fragment_dk_video_setting, this, true); + expandableLayout = findViewById(R.id.el_setting); + + danmakuSizeXSB = findViewById(R.id.xsb_danmaku_size); + danmakuAlphaXSB = findViewById(R.id.xsb_danmaku_alpha); + danmakuSpeedXSB = findViewById(R.id.xsb_danmaku_speed); + danmakuShowAreaXSB = findViewById(R.id.xsb_danmaku_show_area); + + emptyView = findViewById(R.id.empty_view); + } + + private void initData(Context context, DKVideoController dkVideoController) { + mContext = context; + this.dkVideoController = dkVideoController; + danmakuContext = dkVideoController.getDanmakuContext(); + //默认值应该从配置中读取 + danmakuSize = ConfigData.getDanmakuSize(); + danmakuAlpha = ConfigData.getDanmakuAlpha(); + danmakuSpeed = ConfigData.getDanmakuSpeed(); + + + danmakuSizeXSB.setDefaultValue(danmakuSize); + danmakuSizeXSB.setMin(30); + danmakuSizeXSB.setMax(250); + + danmakuAlphaXSB.setDefaultValue(danmakuAlpha); + danmakuAlphaXSB.setMin(0); + danmakuAlphaXSB.setMax(100); + + danmakuSpeedXSB.setDefaultValue(danmakuSpeed); + danmakuSpeedXSB.setMin(1); + danmakuSpeedXSB.setMax(200); + + + } + + private void initMyView() { + setVisibility(GONE); + } + + private void setListeners() { + danmakuSizeXSB.setOnSeekBarListener(this); + danmakuAlphaXSB.setOnSeekBarListener(this); + danmakuSpeedXSB.setOnSeekBarListener(this); + danmakuShowAreaXSB.setOnSeekBarListener(this); + emptyView.setOnClickListener(this); + } + + @Override + public void onValueChanged(XSeekBar seekBar, int newValue) { + int id = seekBar.getId(); + if (id == R.id.xsb_danmaku_size) { + danmakuContext.setScaleTextSize((float) newValue / 100); + danmakuSize = newValue; + } else if (id == R.id.xsb_danmaku_alpha) { + danmakuContext.setDanmakuTransparency((float) newValue / 100);//设置不透明度 + danmakuAlpha = newValue; + } else if (id == R.id.xsb_danmaku_speed) { + danmakuContext.setScrollSpeedFactor((float) (200 - newValue) / 100); + danmakuSpeed = newValue; + } else if (id == R.id.xsb_danmaku_show_area) { + + } + } + + /** + * 弹出、关闭视图 + */ + public void expandableLayoutToggle() { + if (expandableLayout.isExpanded()) { + setVisibility(GONE); + } else { + setVisibility(VISIBLE); + } + expandableLayout.toggle(); + } + + public void changeExpandableLayout(boolean isExpand) { + if (isExpand) { + setVisibility(VISIBLE); + expandableLayout.expand(); + } else { + expandableLayout.collapse(); + setVisibility(GONE); + } + } + + public boolean isExpandableLayout() { + return expandableLayout.isExpanded(); + } + + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.empty_view) { + if (expandableLayout.isExpanded()) { + changeExpandableLayout(false); + } + } + } + + public int getDanmakuSize() { + return danmakuSize; + } + + public int getDanmakuAlpha() { + return danmakuAlpha; + } + + public int getDanmakuSpeed() { + return danmakuSpeed; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + //销毁视图----在这里存储配置 + ConfigData.setDanmakuSize(danmakuSize); + ConfigData.setDanmakuAlpha(danmakuAlpha); + ConfigData.setDanmakuSpeed(danmakuSpeed); + + } + + @Override + public void onStartSlide() { + + } + + @Override + public void onStopSlide() { + + } + + @Override + public void onPositionChange(int slidePosition, int currentPosition, int duration) { + + } + + @Override + public void onBrightnessChange(int percent) { + + } + + @Override + public void onVolumeChange(int percent) { + + } + + @Override + public void attach(@NonNull ControlWrapper controlWrapper) { + mControlWrapper = controlWrapper; + } + + @Nullable + @Override + public View getView() { + return this; + } + + @Override + public void onVisibilityChanged(boolean isVisible, Animation anim) { + + } + + @Override + public void onPlayStateChanged(int playState) { + + } + + @Override + public void onPlayerStateChanged(int playerState) { + + } + + @Override + public void setProgress(int duration, int position) { + + } + + @Override + public void onLockStateChanged(boolean isLocked) { + + } + + +} diff --git a/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoTitleView.java b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoTitleView.java new file mode 100644 index 0000000..672610e --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/controller/videocontroller/component/VideoTitleView.java @@ -0,0 +1,249 @@ +package com.molihua.hlbmerge.controller.videocontroller.component; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.animation.Animation; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.controller.videocontroller.DKVideoController; +import com.xuexiang.xui.widget.textview.MarqueeTextView; + +import java.util.ArrayList; +import java.util.List; + +import xyz.doikki.videoplayer.controller.ControlWrapper; +import xyz.doikki.videoplayer.controller.IControlComponent; +import xyz.doikki.videoplayer.player.VideoView; +import xyz.doikki.videoplayer.util.PlayerUtils; + +/** + * @ClassName: VideoTitleView + * @Author: molihuan + * @Date: 2023/01/01/15:01 + * @Description: + */ +public class VideoTitleView extends FrameLayout implements IControlComponent { + private ControlWrapper mControlWrapper; + + private final RelativeLayout mTitleContainer; + private final MarqueeTextView mTitle; + private final TextView mSysTime;//系统当前时间 + private final ImageView batteryLevel;//电量 + private final ImageView settingIv;//设置按钮 + + private final BatteryReceiver mBatteryReceiver; + private boolean mIsRegister;//是否注册BatteryReceiver + + private String videoName; + private DKVideoController dkVideoController; + + + { + setVisibility(GONE); + LayoutInflater.from(getContext()).inflate(R.layout.fragment_dk_video_title, this, true); + mTitleContainer = findViewById(R.id.rela_titlebar); + ImageView back = findViewById(R.id.back); + back.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + dkVideoController.onBackPressed(); + } + }); + mTitle = findViewById(R.id.title); + + mSysTime = findViewById(R.id.sys_time); + //电量 + batteryLevel = findViewById(R.id.iv_battery); + mBatteryReceiver = new BatteryReceiver(batteryLevel); + + settingIv = findViewById(R.id.iv_setting); + settingIv.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + dkVideoController.getVideoSettingView().expandableLayoutToggle(); + } + }); + + } + + public VideoTitleView(@NonNull Context context, String videoName, DKVideoController dkVideoController) { + super(context); + //初始化数据 + initData(context, videoName, dkVideoController); + //初始化视图 + initMyView(); + //设置监听 + setListeners(); + } + + private void initData(Context context, String videoName, DKVideoController dkVideoController) { + this.videoName = videoName; + this.dkVideoController = dkVideoController; + } + + private void initMyView() { + setTitle(videoName); + } + + private void setListeners() { + } + + public void setTitle(String title) { + //大于25个字就跑马灯 + if (title.length() > 25) { + List<String> titles = new ArrayList<>(); + titles.add(title); + mTitle.startSimpleRoll(titles); + } else { + mTitle.setText(title); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mIsRegister) { + getContext().unregisterReceiver(mBatteryReceiver); + mIsRegister = false; + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (!mIsRegister) { + getContext().registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + mIsRegister = true; + } + } + + @Override + public void attach(@NonNull ControlWrapper controlWrapper) { + mControlWrapper = controlWrapper; + } + + @Override + public View getView() { + return this; + } + + @Override + public void onVisibilityChanged(boolean isVisible, Animation anim) { + //只在全屏时才有效 + //if (mControlWrapper != null && !mControlWrapper.isFullScreen()) return; + if (isVisible) { + + if (getVisibility() == GONE) { + + //不是全屏时 + if (mControlWrapper == null || !mControlWrapper.isFullScreen()) { + mSysTime.setVisibility(GONE); + batteryLevel.setVisibility(GONE); + } else { + mSysTime.setVisibility(VISIBLE); + batteryLevel.setVisibility(VISIBLE); + mSysTime.setText(PlayerUtils.getCurrentSystemTime()); + } + + setVisibility(VISIBLE); + if (anim != null) { + startAnimation(anim); + } + } + } else { + if (getVisibility() == VISIBLE) { + setVisibility(GONE); + if (anim != null) { + startAnimation(anim); + } + } + } + } + + @Override + public void onPlayStateChanged(int playState) { + switch (playState) { + case VideoView.STATE_IDLE: + case VideoView.STATE_START_ABORT: + case VideoView.STATE_PREPARING: + case VideoView.STATE_PREPARED: + case VideoView.STATE_ERROR: + case VideoView.STATE_PLAYBACK_COMPLETED: + setVisibility(VISIBLE); + break; + } + } + + @Override + public void onPlayerStateChanged(int playerState) { + if (playerState == VideoView.PLAYER_FULL_SCREEN) { + if (mControlWrapper.isShowing() && !mControlWrapper.isLocked()) { + setVisibility(VISIBLE); + mSysTime.setText(PlayerUtils.getCurrentSystemTime()); + } + mTitle.setSelected(true); + } else { + setVisibility(GONE); + mTitle.setSelected(false); + } + + Activity activity = PlayerUtils.scanForActivity(getContext()); + if (activity != null && mControlWrapper.hasCutout()) { + int orientation = activity.getRequestedOrientation(); + int cutoutHeight = mControlWrapper.getCutoutHeight(); + if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { + mTitleContainer.setPadding(0, 0, 0, 0); + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { + mTitleContainer.setPadding(cutoutHeight, 0, 0, 0); + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { + mTitleContainer.setPadding(0, 0, cutoutHeight, 0); + } + } + } + + @Override + public void setProgress(int duration, int position) { + + } + + @Override + public void onLockStateChanged(boolean isLocked) { + if (isLocked) { + setVisibility(GONE); + } else { + setVisibility(VISIBLE); + mSysTime.setText(PlayerUtils.getCurrentSystemTime()); + } + } + + private static class BatteryReceiver extends BroadcastReceiver { + private final ImageView pow; + + public BatteryReceiver(ImageView pow) { + this.pow = pow; + } + + @Override + public void onReceive(Context context, Intent intent) { + Bundle extras = intent.getExtras(); + if (extras == null) return; + int current = extras.getInt("level");// 获得当前电量 + int total = extras.getInt("scale");// 获得总电量 + int percent = current * 100 / total; + pow.getDrawable().setLevel(percent); + } + } +} diff --git a/app/src/main/java/com/molihua/hlbmerge/dao/ConfigData.java b/app/src/main/java/com/molihua/hlbmerge/dao/ConfigData.java index 46e3f7a..1c14f45 100644 --- a/app/src/main/java/com/molihua/hlbmerge/dao/ConfigData.java +++ b/app/src/main/java/com/molihua/hlbmerge/dao/ConfigData.java @@ -1,8 +1,11 @@ package com.molihua.hlbmerge.dao; +import com.blankj.molihuan.utilcode.util.AppUtils; import com.molihuan.pathselector.utils.MConstants; import com.tencent.mmkv.MMKV; +import java.io.File; + /** * @ClassName: ConfigData * @Author: molihuan @@ -10,26 +13,35 @@ * @Description: */ public class ConfigData { - //国内 - public final static String TYPE_CACHE_FILE_PATH_INTERNAL = "/tv.danmaku.bili/download"; - //国际 - public final static String TYPE_CACHE_FILE_PATH_ABROAD = "/com.bilibili.app.in/download"; - //平板 - public final static String TYPE_CACHE_FILE_PATH_IPAD = "/tv.danmaku.bilibilihd/download"; - //概念 - public final static String TYPE_CACHE_FILE_PATH_CONCEPT = "/com.bilibili.app.blue/download"; + //国内包名 + public final static String PKGNAME_BILIBILI_INTERNAL = "tv.danmaku.bili"; + //国际包名 + public final static String PKGNAME_BILIBILI_ABROAD = "com.bilibili.app.in"; + //平板包名 + public final static String PKGNAME_BILIBILI_IPAD = "tv.danmaku.bilibilihd"; + //概念包名 + public final static String PKGNAME_BILIBILI_CONCEPT = "com.bilibili.app.blue"; + + //国内默认缓存下载路径 + public final static String TYPE_CACHE_FILE_PATH_INTERNAL = File.separator + PKGNAME_BILIBILI_INTERNAL + File.separator + "download"; + //国际默认缓存下载路径 + public final static String TYPE_CACHE_FILE_PATH_ABROAD = File.separator + PKGNAME_BILIBILI_ABROAD + File.separator + "download"; + //平板默认缓存下载路径 + public final static String TYPE_CACHE_FILE_PATH_IPAD = File.separator + PKGNAME_BILIBILI_IPAD + File.separator + "download"; + //概念默认缓存下载路径 + public final static String TYPE_CACHE_FILE_PATH_CONCEPT = File.separator + PKGNAME_BILIBILI_CONCEPT + File.separator + "download"; + //完成文件路径 - public final static String TYPE_OUTPUT_FILE_PATH_COMPLETE = "/bilibili缓存合并/complete"; + public final static String TYPE_OUTPUT_FILE_PATH_COMPLETE = "/bilibili视频合并/complete"; //临时文件路径 - public final static String TYPE_OUTPUT_FILE_PATH_TEMP = MConstants.DEFAULT_ROOTPATH + "/bilibili缓存合并/temp"; + public final static String TYPE_OUTPUT_FILE_PATH_TEMP = MConstants.DEFAULT_ROOTPATH + "/bilibili视频合并/temp"; private final static MMKV kv = MMKV.defaultMMKV(); + //缓存文件路径 private String cacheFilePath; //输出文件路径 private String outputFilePath; - //第一次使用 - private boolean firstUse; //是否导出弹幕 private boolean exportDanmaku; //导出类型,0有声音视频,1无声音视频,2仅仅音频 @@ -38,73 +50,135 @@ public class ConfigData { private boolean agreeTerms; //是否开启弹幕 private boolean openBarrage; - + //弹幕大小 + private int danmakuSize; + //弹幕不透明度 + private int danmakuAlpha; + //弹幕速度 + private int danmakuSpeed; + + /** + * 每当需要新增配置就 + * 新增一个判断 + */ public static void init() { - //第一次 - if (!kv.containsKey("firstUse")) { - kv.encode("firstUse", false); - kv.encode("cacheFilePath", MConstants.PATH_ANRROID_DATA + TYPE_CACHE_FILE_PATH_INTERNAL); - kv.encode("outputFilePath", MConstants.DEFAULT_ROOTPATH + TYPE_OUTPUT_FILE_PATH_COMPLETE); - kv.encode("exportDanmaku", false); - kv.encode("exportType", 0); - kv.encode("agreeTerms", false); - kv.encode("openBarrage", false); + //不存在配置版本号为0则 + if (!kv.containsKey("configDataVersion")) { + kv.encode("configDataVersion", true); + setCacheFilePathByInstalledBili(); + setOutputFilePath(MConstants.DEFAULT_ROOTPATH + TYPE_OUTPUT_FILE_PATH_COMPLETE); + setExportDanmaku(false); + setExportType(0); + setAgreeTerms(false); + setOpenBarrage(true); + } + //新增配置1 +// if (!kv.containsKey("ffmpegVersion")) { +// kv.encode("ffmpegVersion", 0); +// } + + if (!kv.containsKey("danmakuSize")) { + setDanmakuSize(100); + setDanmakuAlpha(100); + setDanmakuSpeed(100); } - } - public static String getCacheFilePath() { - return kv.decodeString("cacheFilePath"); + } - public static void setCacheFilePath(String cacheFilePath) { + /** + * 根据安装的bilibili版本设置对应的缓存路径 + */ + public static void setCacheFilePathByInstalledBili() { + String cacheFilePath; + + if (AppUtils.isAppInstalled(PKGNAME_BILIBILI_INTERNAL)) { + cacheFilePath = MConstants.PATH_ANRROID_DATA + TYPE_CACHE_FILE_PATH_INTERNAL; + } else if (AppUtils.isAppInstalled(PKGNAME_BILIBILI_ABROAD)) { + cacheFilePath = MConstants.PATH_ANRROID_DATA + TYPE_CACHE_FILE_PATH_ABROAD; + } else if (AppUtils.isAppInstalled(PKGNAME_BILIBILI_IPAD)) { + cacheFilePath = MConstants.PATH_ANRROID_DATA + TYPE_CACHE_FILE_PATH_IPAD; + } else if (AppUtils.isAppInstalled(PKGNAME_BILIBILI_CONCEPT)) { + cacheFilePath = MConstants.PATH_ANRROID_DATA + TYPE_CACHE_FILE_PATH_CONCEPT; + } else { + cacheFilePath = MConstants.PATH_ANRROID_DATA + TYPE_CACHE_FILE_PATH_INTERNAL; + } + kv.encode("cacheFilePath", cacheFilePath); + } - public static String getOutputFilePath() { - return kv.decodeString("outputFilePath"); + + public static String getCacheFilePath() { + return kv.decodeString("cacheFilePath"); } - public static void setOutputFilePath(String outputFilePath) { - kv.encode("outputFilePath", outputFilePath); + public static boolean setCacheFilePath(String cacheFilePath) { + return kv.encode("cacheFilePath", cacheFilePath); } - public static boolean isFirstUse() { - return kv.decodeBool("firstUse"); + public static String getOutputFilePath() { + return kv.decodeString("outputFilePath"); } - public static void setFirstUse(boolean firstUse) { - kv.encode("firstUse", firstUse); + public static boolean setOutputFilePath(String outputFilePath) { + return kv.encode("outputFilePath", outputFilePath); } public static boolean isExportDanmaku() { return kv.decodeBool("exportDanmaku"); } - public static void setExportDanmaku(boolean exportDanmaku) { - kv.encode("exportDanmaku", exportDanmaku); + public static boolean setExportDanmaku(boolean exportDanmaku) { + return kv.encode("exportDanmaku", exportDanmaku); } public static int getExportType() { return kv.decodeInt("exportType"); } - public static void setExportType(int exportType) { - kv.encode("exportType", exportType); + public static boolean setExportType(int exportType) { + return kv.encode("exportType", exportType); } public static boolean isAgreeTerms() { return kv.decodeBool("agreeTerms"); } - public static void setAgreeTerms(boolean agreeTerms) { - kv.encode("agreeTerms", agreeTerms); + public static boolean setAgreeTerms(boolean agreeTerms) { + return kv.encode("agreeTerms", agreeTerms); } public static boolean isOpenBarrage() { return kv.decodeBool("openBarrage"); } - public static void setOpenBarrage(boolean openBarrage) { - kv.encode("openBarrage", openBarrage); + public static boolean setOpenBarrage(boolean openBarrage) { + return kv.encode("openBarrage", openBarrage); + } + + + public static int getDanmakuSize() { + return kv.decodeInt("danmakuSize"); + } + + public static boolean setDanmakuSize(int danmakuSize) { + return kv.encode("danmakuSize", danmakuSize); + } + + public static int getDanmakuAlpha() { + return kv.decodeInt("danmakuAlpha"); + } + + public static boolean setDanmakuAlpha(int danmakuAlpha) { + return kv.encode("danmakuAlpha", danmakuAlpha); + } + + public static int getDanmakuSpeed() { + return kv.decodeInt("danmakuSpeed"); + } + + public static boolean setDanmakuSpeed(int danmakuSpeed) { + return kv.encode("danmakuSpeed", danmakuSpeed); } } diff --git a/app/src/main/java/com/molihua/hlbmerge/dialog/impl/CopyProgressDialog.java b/app/src/main/java/com/molihua/hlbmerge/dialog/impl/CopyProgressDialog.java new file mode 100644 index 0000000..8115d45 --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/dialog/impl/CopyProgressDialog.java @@ -0,0 +1,123 @@ +package com.molihua.hlbmerge.dialog.impl; + +import android.content.Context; +import android.content.DialogInterface; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.blankj.molihuan.utilcode.util.FileUtils; +import com.molihua.hlbmerge.utils.UriTool; +import com.molihuan.pathselector.entity.FileBean; +import com.molihuan.pathselector.fragment.BasePathSelectFragment; +import com.molihuan.pathselector.utils.FileTools; +import com.xuexiang.xtask.XTask; +import com.xuexiang.xtask.core.ITaskChainEngine; +import com.xuexiang.xtask.core.param.ITaskResult; +import com.xuexiang.xtask.core.step.impl.TaskChainCallbackAdapter; +import com.xuexiang.xtask.core.step.impl.TaskCommand; +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction; +import com.xuexiang.xui.widget.dialog.materialdialog.GravityEnum; +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog; + +import java.io.File; +import java.util.List; + +/** + * @ClassName: CopyProgressDialog + * @Author: molihuan + * @Date: 2022/12/28/19:27 + * @Description: + */ +public class CopyProgressDialog { + + private static boolean dataUseUri; + + public static MaterialDialog showCopyProgressDialog(List<FileBean> copySrcFileList, String copySrcParentPath, String currentPath, Context context, TextView tv, BasePathSelectFragment pathSelectFragment) { + + MaterialDialog materialDialog = new MaterialDialog.Builder(context) + .title("提示") + .content("正在复制文件...") + .contentGravity(GravityEnum.CENTER) + .progress(false, copySrcFileList.size(), true) + .cancelable(false) + .negativeText("取消") + .onNegative(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + + dialog.dismiss(); + } + }) + .showListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + CopyProgressDialog.startCopy(copySrcFileList, copySrcParentPath, currentPath, (MaterialDialog) dialog, tv, pathSelectFragment); + } + }) + .show(); + + return materialDialog; + } + + /** + * 把源文件路径复制到currentPath下 + * + * @param copySrcFileList + * @param dialog + */ + private static void startCopy(List<FileBean> copySrcFileList, String copySrcParentPath, String currentPath, MaterialDialog dialog, TextView tv, BasePathSelectFragment pathSelectFragment) { + + XTask.getTaskChain() + .addTask(XTask.getTask(new TaskCommand() { + @Override + public void run() throws Exception { + + for (FileBean fileBean : copySrcFileList) { + + XTask.postToMain(new Runnable() { + @Override + public void run() { + dialog.setContent("正在复制:" + fileBean.getPath()); + } + }); + + //是否需要使用uri + CopyProgressDialog.dataUseUri = FileTools.underAndroidDataUseUri(fileBean.getPath()); + if (CopyProgressDialog.dataUseUri) { + //开始复制DocumentFile + UriTool.copyDocumentFile(pathSelectFragment, FileTools.getParentPath(fileBean.getPath()), fileBean.getPath(), currentPath); + } else { + //开始复制File + FileUtils.copy(fileBean.getPath(), fileBean.getPath().replace(copySrcParentPath, currentPath), new FileUtils.OnReplaceListener() { + @Override + public boolean onReplace(File file, File file1) { + return false; + } + }); + + } + + dialog.incrementProgress(1); + + } + + } + })) + .setTaskChainCallback(new TaskChainCallbackAdapter() { + @Override + public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) { + //更新ui + dialog.setContent("处理完成"); + dialog.setActionButton(DialogAction.NEGATIVE, "关闭"); + + pathSelectFragment.updateFileList(); + tv.setText("复制"); + copySrcFileList.clear(); + } + }) + .start(); + + + } +} diff --git a/app/src/main/java/com/molihua/hlbmerge/dialog/impl/MergeProgressDialog.java b/app/src/main/java/com/molihua/hlbmerge/dialog/impl/MergeProgressDialog.java index 7e5460a..ca0b98d 100644 --- a/app/src/main/java/com/molihua/hlbmerge/dialog/impl/MergeProgressDialog.java +++ b/app/src/main/java/com/molihua/hlbmerge/dialog/impl/MergeProgressDialog.java @@ -40,6 +40,8 @@ public class MergeProgressDialog { //用户是否选择的标志位 public static boolean FLAG_USER_HANDLE = false; + //退出执行ffmpeg命令 + public static boolean FLAG_EXIT_RUN_COMMAND = false; public static final String CMD_TEMPLATE = "ffmpeg -i %s -i %s -c copy %s"; /** @@ -49,6 +51,8 @@ public class MergeProgressDialog { * @return */ public static MaterialDialog showMergeProgressDialog(List<CacheFile> cacheFileList, Fragment fragment) { + //初始化 + FLAG_EXIT_RUN_COMMAND = false; Context context = fragment.getContext(); Objects.requireNonNull(context, "context is null"); @@ -74,6 +78,9 @@ public static MaterialDialog showMergeProgressDialog(List<CacheFile> cacheFileLi .onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + RxFfmpegTools.exitRunCommand(); + MergeProgressDialog.FLAG_EXIT_RUN_COMMAND = true; + RxFfmpegTools.exitRunCommand(); dialog.dismiss(); } }) @@ -116,10 +123,15 @@ public void run() throws Exception { String completeOutPathSuf; for (int i = 0; i < cacheFileList.size(); i++) { + //是否退出命令的执行 + if (MergeProgressDialog.FLAG_EXIT_RUN_COMMAND) { + break; + } + srcCacheFile = cacheFileList.get(i); //将uri转换为file并把路径返回存入CacheFile中 - cacheFile = MergeProgressDialog.cacheFileUri2File(srcCacheFile); + cacheFile = MergeProgressDialog.cacheFileUri2File(srcCacheFile, dialog); //创建输出目录 subOutPath = outRoot + File.separator + cacheFile.getCollectionName(); @@ -180,6 +192,7 @@ public void run() throws Exception { public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) { //更新ui dialog.setContent(String.format("成功数:%s,失败数:%s,\n合并文件保存在%s目录下", ffmpegCallback.getSuccessNum(), ffmpegCallback.getFailNum(), outRoot)); + dialog.setActionButton(DialogAction.NEGATIVE, "关闭"); } }) .start(); @@ -192,16 +205,26 @@ public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITas * @param cacheFile * @return 转换成功或不是Android11返回true */ - public static CacheFile cacheFileUri2File(CacheFile cacheFile) { + public static CacheFile cacheFileUri2File(CacheFile cacheFile, MaterialDialog dialog) { if (cacheFile.getUseUri()) { - //uri转byte - byte[] bytesAudio = UriUtils.uri2Bytes(Uri.parse(cacheFile.getAudioPath())); - byte[] bytesVideo = UriUtils.uri2Bytes(Uri.parse(cacheFile.getVideoPath())); - byte[] bytesDanmaku = UriUtils.uri2Bytes(Uri.parse(cacheFile.getDanmakuPath())); //获取临时文件名 String audioTemp = ConfigData.TYPE_OUTPUT_FILE_PATH_TEMP + "/audio.mp3"; String videoTemp = ConfigData.TYPE_OUTPUT_FILE_PATH_TEMP + "/video.mp4"; String danmakuTemp = ConfigData.TYPE_OUTPUT_FILE_PATH_TEMP + "/danmaku.xml"; + + XTask.postToMain(new Runnable() { + @Override + public void run() { + dialog.setContent("正在为你复制缓存文件,\n" + ConfigData.getCacheFilePath() + "--->" + ConfigData.TYPE_OUTPUT_FILE_PATH_TEMP); + } + }); + + + //uri转byte + byte[] bytesAudio = UriUtils.uri2Bytes(Uri.parse(cacheFile.getAudioPath())); + byte[] bytesVideo = UriUtils.uri2Bytes(Uri.parse(cacheFile.getVideoPath())); + byte[] bytesDanmaku = UriUtils.uri2Bytes(Uri.parse(cacheFile.getDanmakuPath())); + //删除已经存在的临时文件名 FileUtils.delete(audioTemp); FileUtils.delete(videoTemp); @@ -224,7 +247,7 @@ public static CacheFile cacheFileUri2File(CacheFile cacheFile) { tempCacheFile.setVideoPath(videoTemp); tempCacheFile.setDanmakuPath(danmakuTemp); } - + return tempCacheFile; } else { return cacheFile; diff --git a/app/src/main/java/com/molihua/hlbmerge/dialog/impl/StatementDialog.java b/app/src/main/java/com/molihua/hlbmerge/dialog/impl/StatementDialog.java new file mode 100644 index 0000000..19b3cfc --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/dialog/impl/StatementDialog.java @@ -0,0 +1,85 @@ +package com.molihua.hlbmerge.dialog.impl; + +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.blankj.molihuan.utilcode.util.ActivityUtils; +import com.blankj.molihuan.utilcode.util.ResourceUtils; +import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.activity.impl.HtmlActivity; +import com.molihua.hlbmerge.dao.ConfigData; +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction; +import com.xuexiang.xui.widget.dialog.materialdialog.GravityEnum; +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog; + +/** + * @ClassName: StatementDialog + * @Author: molihuan + * @Date: 2022/12/27/23:01 + * @Description: 声明弹窗 + */ +public class StatementDialog { + + public interface IButtonCallback { + void onClick(MaterialDialog dialog, DialogAction which); + } + + private static View getCustomViewOfDialog(Context context) { + View view = LayoutInflater.from(context).inflate(R.layout.dialog_statement, null);//获取自定义布局 + TextView tx_statement = view.findViewById(R.id.tx_statement); + String statementContent = ResourceUtils.readAssets2String("statement.txt");//从Assets中读取 + tx_statement.setText(statementContent); + return view; + } + + public static void showStatementDialog(Context context) { + showStatementDialog(context, null); + } + + public static void showStatementDialog(Context context, IButtonCallback positiveCallback) { + new MaterialDialog.Builder(context) + .autoDismiss(false)//是否点击按钮自动关闭 + .cancelable(false)//外部不可点击 + .customView(getCustomViewOfDialog(context), true)//布局可以用view + .title("用户协议") + .titleGravity(GravityEnum.CENTER)//标题居中 + .neutralText("隐私政策") + .onNeutral(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + Intent intentPrivacy = new Intent(context, HtmlActivity.class); + intentPrivacy.putExtra("url", "file:///android_asset/privacy.html"); + intentPrivacy.putExtra("title", "隐私政策"); + context.startActivity(intentPrivacy); + } + }) + .positiveText("已阅读并同意") + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + if (positiveCallback != null) { + positiveCallback.onClick(dialog, which); + } + ConfigData.setAgreeTerms(true); + dialog.dismiss(); + } + }) + .negativeText("不同意") + .onNegative(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + ConfigData.setAgreeTerms(false); + ActivityUtils.finishAllActivities(); //退出所有activity + System.exit(0);//退出应用 + } + }) + .show(); + } + + +} diff --git a/app/src/main/java/com/molihua/hlbmerge/fragment/impl/BackTitlebarFragment.java b/app/src/main/java/com/molihua/hlbmerge/fragment/impl/BackTitlebarFragment.java new file mode 100644 index 0000000..94b34f3 --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/fragment/impl/BackTitlebarFragment.java @@ -0,0 +1,108 @@ +package com.molihua.hlbmerge.fragment.impl; + +import android.view.View; +import android.widget.TextView; + +import com.blankj.molihuan.utilcode.util.StringUtils; +import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.fragment.BaseFragment; +import com.xuexiang.xui.widget.button.shadowbutton.ShadowImageView; +import com.xuexiang.xui.widget.textview.autofit.AutoFitTextView; + +/** + * @ClassName: BackTitlebarFragment + * @Author: molihuan + * @Date: 2022/11/26/14:30 + * @Description: 通用返回titlebar + */ +public class BackTitlebarFragment extends BaseFragment implements View.OnClickListener { + private String title; + private String subtitle; + + private ShadowImageView btn_back_toolbar; + private AutoFitTextView main_title_toolbar; + private AutoFitTextView subtitle_toolbar; + + private TextView rightOptionTv; + private String rightOptionText; + private IClickListener rightOptionClickListener; + + public interface IClickListener { + void onClick(View v); + } + + public BackTitlebarFragment setRightOption(String rightOptionText, IClickListener rightOptionClickListener) { + this.rightOptionText = rightOptionText; + this.rightOptionClickListener = rightOptionClickListener; + return this; + } + + public BackTitlebarFragment(String title) { + this.title = title; + } + + public BackTitlebarFragment(String title, String subtitle) { + this.title = title; + this.subtitle = subtitle; + } + + + @Override + public int setFragmentViewId() { + return R.layout.fragment_back_titlebar; + } + + @Override + public void getComponents(View view) { + btn_back_toolbar = view.findViewById(R.id.btn_back_toolbar); + main_title_toolbar = view.findViewById(R.id.main_title_toolbar); + subtitle_toolbar = view.findViewById(R.id.subtitle_toolbar); + rightOptionTv = view.findViewById(R.id.tv_right_option); + } + + @Override + public void initData() { + + } + + @Override + public void initView() { + if (StringUtils.isTrimEmpty(title)) { + main_title_toolbar.setVisibility(View.INVISIBLE); + } else { + main_title_toolbar.setText(title); + } + + if (StringUtils.isTrimEmpty(subtitle)) { + subtitle_toolbar.setVisibility(View.GONE); + } else { + subtitle_toolbar.setText(subtitle); + } + + if (rightOptionText != null) { + rightOptionTv.setVisibility(View.VISIBLE); + rightOptionTv.setText(rightOptionText); + rightOptionTv.setOnClickListener(this); + } + } + + @Override + public void setListeners() { + btn_back_toolbar.setOnClickListener(this); + + } + + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.btn_back_toolbar) { + mActivity.onBackPressed(); + } else if (id == R.id.tv_right_option) { + rightOptionClickListener.onClick(v); + } + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainCompleteFragment.java b/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainCompleteFragment.java index f6d6aab..8a4ca01 100644 --- a/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainCompleteFragment.java +++ b/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainCompleteFragment.java @@ -1,8 +1,17 @@ package com.molihua.hlbmerge.fragment.impl; +import android.content.Intent; import android.view.View; +import android.widget.TextView; +import androidx.annotation.NonNull; + +import com.blankj.molihuan.utilcode.util.FileUtils; +import com.blankj.molihuan.utilcode.util.ToastUtils; import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.activity.impl.PlayVideoActivity; +import com.molihua.hlbmerge.dao.ConfigData; +import com.molihua.hlbmerge.dialog.impl.CopyProgressDialog; import com.molihua.hlbmerge.fragment.AbstractMainFragment; import com.molihuan.pathselector.PathSelector; import com.molihuan.pathselector.entity.FileBean; @@ -12,7 +21,10 @@ import com.molihuan.pathselector.listener.FileItemListener; import com.molihuan.pathselector.utils.MConstants; import com.molihuan.pathselector.utils.Mtools; +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction; +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog; +import java.util.ArrayList; import java.util.List; /** @@ -24,6 +36,13 @@ public class MainCompleteFragment extends AbstractMainFragment { private PathSelectFragment pathSelectFragment; + private List<FileBean> copySrcFileList; + private String copySrcParentPath; + + private TextView copyBtnTextView; + + private boolean stopDeleteFlag; + @Override public int setFragmentViewId() { return R.layout.fragment_main_complete; @@ -49,46 +68,160 @@ public void setListeners() { } - @Override - public boolean onBackPressed() { - if (pathSelectFragment != null && pathSelectFragment.onBackPressed()) { - return true; - } - return false; + public PathSelectFragment getPathSelectFragment() { + return pathSelectFragment; + } + + public void refreshFileList() { + pathSelectFragment.updateFileList(); } - /** - * 文件选择器 - */ private void openFileChoose() { pathSelectFragment = PathSelector.build(this, MConstants.BUILD_FRAGMENT) + .setShowTabbarFragment(false) + .setRootPath(ConfigData.getOutputFilePath()) .setFrameLayoutId(R.id.main_complete_view) .setShowTitlebarFragment(false) - .setShowFileTypes("mp4", "xml", "mp3", "") + .setAlwaysShowHandleFragment(true) + .setSelectFileTypes("mp4", "xml", "mp3", "m4s", "") .setFileItemListener(new FileItemListener() { @Override public boolean onClick(View v, FileBean file, String currentPath, BasePathSelectFragment pathSelectFragment) { - Mtools.toast(file.getPath()); + if (!file.isDir()) { + String fileExtension = file.getFileExtension(); + if ("mp4".equals(fileExtension) || "mp3".equals(fileExtension) || "m4s".equals(fileExtension)) { + Intent intent = new Intent(mActivity, PlayVideoActivity.class); + intent.putExtra("videoPath", file.getPath()); + startActivity(intent); + } else { + ToastUtils.make().show("此文件不支持播放"); + } + } + return false; } }) .setHandleItemListeners( new CommonItemListener("全选") { @Override - public boolean onClick(View v, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) { + public boolean onClick(View v, TextView tv, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) { + pathSelectFragment.openCloseMultipleMode(true); + if (tv.getText().equals("全选")) { + pathSelectFragment.selectAllFile(true); + tv.setText("全不选"); + } else { + pathSelectFragment.selectAllFile(false); + tv.setText("全选"); + } + pathSelectFragment.refreshFileList(); + + return false; + } + }, + new CommonItemListener("复制") { + @Override + public boolean onClick(View v, TextView tv, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) { + copyBtnTextView = tv; + if (tv.getText().equals("复制")) { + + if (selectedFiles == null || selectedFiles.size() == 0) { + Mtools.toast("你还没有选择文件捏!(长按进行选择)"); + return false; + } +//TODO 设置取消复制 + +// List<CommonItemListener> handleItemListeners = pathSelectFragment.getHandleItemListeners(); +// for (CommonItemListener handleItemListener : handleItemListeners) { +// if ("取消".equals(handleItemListener.getFontBean().getText())) { +// handleItemListener.getFontBean().setText("取消复制"); +// pathSelectFragment.refreshHandleList(); +// break; +// } +// } + + tv.setText("粘贴"); + + //获取源文件路径 + copySrcFileList = new ArrayList<>(selectedFiles); + //获取源文件父目录 + copySrcParentPath = currentPath; + pathSelectFragment.openCloseMultipleMode(false); + + } else { + if (copySrcFileList == null || copySrcFileList.size() == 0) { + return false; + } + CopyProgressDialog.showCopyProgressDialog(copySrcFileList, copySrcParentPath, currentPath, mActivity, tv, pathSelectFragment); + + } + return false; } }, new CommonItemListener("删除") { @Override - public boolean onClick(View v, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) { + public boolean onClick(View v, TextView tv, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) { + if (selectedFiles == null || selectedFiles.size() == 0) { + Mtools.toast("你还没有选择文件捏!(长按进行选择)"); + return false; + } + //初始化标志位 + stopDeleteFlag = false; + + new MaterialDialog.Builder(mActivity) + .title("删除") + .content("你确定要删除此文件吗?(此操作无法撤回)") + .cancelable(false) + .positiveText("确定") + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + for (FileBean selectedFile : selectedFiles) { + if (stopDeleteFlag) { + break; + } + FileUtils.delete(selectedFile.getPath()); + } + pathSelectFragment.updateFileList(); + pathSelectFragment.openCloseMultipleMode(false); + } + }) + .negativeText("取消") + .onNegative(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + stopDeleteFlag = true; + dialog.dismiss(); + } + }) + .show(); + + return false; + } + }, + new CommonItemListener("取消") { + @Override + public boolean onClick(View v, TextView tv, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) { + pathSelectFragment.openCloseMultipleMode(false); + if (copyBtnTextView != null) { + copyBtnTextView.setText("复制"); + } + tv.setText("取消"); return false; } } ) .show(); - + + } + + @Override + public boolean onBackPressed() { + if (pathSelectFragment != null && pathSelectFragment.onBackPressed()) { + return true; + } + return false; } diff --git a/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainFileShowFragment.java b/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainFileShowFragment.java index a30e5fd..675ea6a 100644 --- a/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainFileShowFragment.java +++ b/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainFileShowFragment.java @@ -67,7 +67,6 @@ public void initData() { //权限申请 // UriTool.grantedUriPermission(ConfigData.getCacheFilePath(), this); - pathCacheFileManager = new PathCacheFileManager(); uriCacheFileManager = new UriCacheFileManager(this); @@ -285,15 +284,19 @@ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent d //保存这个uri目录的访问权限 if (VersionTool.isAndroid11()) { if (requestCode == PermissionsTools.PERMISSION_REQUEST_CODE) { - Uri uri; - if ((uri = data.getData()) != null) { - mActivity.getContentResolver() - .takePersistableUriPermission(uri, - data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - ); + if (data != null) { + Uri uri; + if ((uri = data.getData()) != null) { + mActivity.getContentResolver() + .takePersistableUriPermission(uri, + data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + ); + } + //获取数据刷新列表 + updateCollectionFileList(); + refreshCacheFileList(); } - //更新列表数据 - updateCollectionFileList(); + } } super.onActivityResult(requestCode, resultCode, data); diff --git a/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainTitlebarFragment.java b/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainTitlebarFragment.java index df1796e..59abd9d 100644 --- a/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainTitlebarFragment.java +++ b/app/src/main/java/com/molihua/hlbmerge/fragment/impl/MainTitlebarFragment.java @@ -4,8 +4,12 @@ import android.widget.ImageView; import android.widget.TextView; +import androidx.viewpager.widget.ViewPager; + import com.blankj.molihuan.utilcode.util.RegexUtils; +import com.blankj.molihuan.utilcode.util.ToastUtils; import com.molihua.hlbmerge.R; +import com.molihua.hlbmerge.dao.ConfigData; import com.molihua.hlbmerge.entity.CacheFile; import com.molihua.hlbmerge.fragment.AbstractMainTitlebarFragment; import com.molihua.hlbmerge.service.BaseCacheFileManager; @@ -29,6 +33,12 @@ public class MainTitlebarFragment extends AbstractMainTitlebarFragment implement private MaterialSearchView searchView; + private ViewPager viewPager; + + public enum ImgView { + REFRESH, SEARCH + } + @Override public int setFragmentViewId() { @@ -46,7 +56,7 @@ public void getComponents(View view) { @Override public void initData() { - + viewPager = abstractMainActivity.getViewPager(); } @Override @@ -82,25 +92,39 @@ public void onClick(View v) { abstractMainActivity.showHideNavigation(true); } else if (id == R.id.imgv_refresh_titlebar) { - //多选模式不允许刷新 - if (abstractMainActivity.isMultipleSelectionMode()) { - return; - } - List<CacheFile> allCacheFileList = abstractMainActivity.getAllCacheFileList(); - - if (allCacheFileList.size() == 0) { - return; - } + switch (viewPager.getCurrentItem()) { + case 0: - CacheFile cacheFile = allCacheFileList.get(0); - if (cacheFile.getFlag() == BaseCacheFileManager.FLAG_CACHE_FILE_COLLECTION) { - abstractMainActivity.updateCollectionFileList(); - } else { - abstractMainActivity.updateChapterFileList(); + //多选模式不允许刷新 + if (abstractMainActivity.isMultipleSelectionMode()) { + return; + } + + List<CacheFile> allCacheFileList = abstractMainActivity.getAllCacheFileList(); + + if (allCacheFileList.size() == 0) { + ToastUtils.showLong("当前缓存路径为:" + ConfigData.getCacheFilePath() + "\n请检测‘设置’中缓存路径是否正确,哔哩哔哩版本是否正确,如果正确请再次刷新"); + abstractMainActivity.updateCollectionFileList(); + return; + } + + CacheFile cacheFile = allCacheFileList.get(0); + if (cacheFile.getFlag() == BaseCacheFileManager.FLAG_CACHE_FILE_COLLECTION) { + abstractMainActivity.updateCollectionFileList(); + } else { + abstractMainActivity.updateChapterFileList(); + } + + abstractMainActivity.refreshCacheFileList(); + + break; + case 1: + abstractMainActivity.refreshCompleteFileList(); + break; + default: } - abstractMainActivity.refreshCacheFileList(); } else if (id == R.id.imgv_seach_titlebar) { @@ -223,5 +247,19 @@ public void showHideImgView(boolean status) { } } + @Override + public void showTitleImgView(ImgView showImg) { + showHideImgView(false); + switch (showImg) { + case REFRESH: + refreshImgView.setVisibility(View.VISIBLE); + break; + case SEARCH: + searchImgView.setVisibility(View.VISIBLE); + break; + default: + } + } + } diff --git a/app/src/main/java/com/molihua/hlbmerge/interfaces/IMainTitlebarFragment.java b/app/src/main/java/com/molihua/hlbmerge/interfaces/IMainTitlebarFragment.java index cbc7cd2..abd02d8 100644 --- a/app/src/main/java/com/molihua/hlbmerge/interfaces/IMainTitlebarFragment.java +++ b/app/src/main/java/com/molihua/hlbmerge/interfaces/IMainTitlebarFragment.java @@ -1,12 +1,15 @@ package com.molihua.hlbmerge.interfaces; +import com.molihua.hlbmerge.fragment.impl.MainTitlebarFragment; import com.xuexiang.xui.widget.searchview.MaterialSearchView; public interface IMainTitlebarFragment { void setMainTitle(String text); - + void showHideImgView(boolean status); + void showTitleImgView(MainTitlebarFragment.ImgView showImg); + void showHideSearchView(boolean status); MaterialSearchView getSearchView(); diff --git a/app/src/main/java/com/molihua/hlbmerge/service/impl/PathCacheFileManager.java b/app/src/main/java/com/molihua/hlbmerge/service/impl/PathCacheFileManager.java index 582ef69..972d93e 100644 --- a/app/src/main/java/com/molihua/hlbmerge/service/impl/PathCacheFileManager.java +++ b/app/src/main/java/com/molihua/hlbmerge/service/impl/PathCacheFileManager.java @@ -29,16 +29,31 @@ public List<CacheFile> updateCollectionFileList(String path, List<CacheFile> cac String[] names = new String[2]; //获取所有的合集 File[] collectionFile = FileTools.getCollectionChapterFile(path); - if (collectionFile == null) { + + + if (collectionFile == null || collectionFile.length == 0) { return cacheFileList; } + + for (int i = 0; i < collectionFile.length; i++) { + + if (collectionFile[i].listFiles() == null || collectionFile[i].listFiles().length == 0) { + return cacheFileList; + } + //获取每一个集合中的第一个章节 - File oneChapterPath = Objects.requireNonNull(collectionFile[i].listFiles())[0]; + File oneChapterPath = collectionFile[i].listFiles()[0]; //获取章节里需要的路径 needPath = FileTools.getNeedPath(oneChapterPath, needPath); + //获取合集名称和章节名称 names = FileTools.getCollectionChapterName(needPath[2], names); + + if (names == null) { + return cacheFileList; + } + cacheFileList.add( new CacheFile() .setFlag(BaseCacheFileManager.FLAG_CACHE_FILE_COLLECTION) diff --git a/app/src/main/java/com/molihua/hlbmerge/service/impl/RxFFmpegCallback.java b/app/src/main/java/com/molihua/hlbmerge/service/impl/RxFFmpegCallback.java index 0590aa2..284e2eb 100644 --- a/app/src/main/java/com/molihua/hlbmerge/service/impl/RxFFmpegCallback.java +++ b/app/src/main/java/com/molihua/hlbmerge/service/impl/RxFFmpegCallback.java @@ -1,5 +1,6 @@ package com.molihua.hlbmerge.service.impl; +import com.xuexiang.xtask.XTask; import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog; import io.microshow.rxffmpeg.RxFFmpegSubscriber; @@ -27,7 +28,12 @@ public void onFinish() { @Override public void onProgress(int progress, long progressTime) { - dialog.setContent("已处理progressTime=" + (double) progressTime / 1000000 + "秒"); + XTask.postToMain(new Runnable() { + @Override + public void run() { + dialog.setContent("已处理progressTime=" + (double) progressTime / 1000000 + "秒"); + } + }); } @Override diff --git a/app/src/main/java/com/molihua/hlbmerge/service/impl/UriCacheFileManager.java b/app/src/main/java/com/molihua/hlbmerge/service/impl/UriCacheFileManager.java index 78a9665..b9c1540 100644 --- a/app/src/main/java/com/molihua/hlbmerge/service/impl/UriCacheFileManager.java +++ b/app/src/main/java/com/molihua/hlbmerge/service/impl/UriCacheFileManager.java @@ -37,14 +37,14 @@ public List<CacheFile> updateCollectionFileList(String path, List<CacheFile> cac Uri[] needUri = new Uri[4]; String[] names = new String[2]; //获取所有的合集路径 - DocumentFile[] collectionFiles = UriTool.getCollectionChapterFile(fragment, path); + List<DocumentFile> collectionFiles = UriTool.getCollectionChapterFile(fragment, path); if (collectionFiles == null) { return cacheFileList; } - for (int i = 0; i < collectionFiles.length; i++) { + for (int i = 0; i < collectionFiles.size(); i++) { //获取每一个集合中的第一个章节路径 - DocumentFile oneChapterPath = Objects.requireNonNull(collectionFiles[i].listFiles())[0]; + DocumentFile oneChapterPath = Objects.requireNonNull(collectionFiles.get(i).listFiles())[0]; //获取章节里需要的Uri needUri = UriTool.getNeedUri(oneChapterPath, needUri); @@ -56,7 +56,7 @@ public List<CacheFile> updateCollectionFileList(String path, List<CacheFile> cac new CacheFile() .setFlag(BaseCacheFileManager.FLAG_CACHE_FILE_COLLECTION) .setWholeVisibility(View.VISIBLE) - .setCollectionPath(path + File.separator + collectionFiles[i].getName()) + .setCollectionPath(path + File.separator + collectionFiles.get(i).getName()) .setCollectionName(names[0]) .setChapterName(names[1]) .setAudioPath(needUri[0].toString()) @@ -101,16 +101,16 @@ public List<CacheFile> updateChapterFileList(String collectionPath, List<CacheFi Uri[] needUri = new Uri[4]; String[] names = new String[2]; //获取一个合集下面所有的章节 - DocumentFile[] chapterFile = UriTool.getCollectionChapterFile(fragment, collectionPath); + List<DocumentFile> chapterFile = UriTool.getCollectionChapterFile(fragment, collectionPath); if (chapterFile == null) { return cacheFileList; } - for (int i = 0; i < chapterFile.length; i++) { + for (int i = 0; i < chapterFile.size(); i++) { //获取章节里需要的Uri - needUri = UriTool.getNeedUri(chapterFile[i], needUri); + needUri = UriTool.getNeedUri(chapterFile.get(i), needUri); //获取合集名称和章节名称 names = UriTool.getCollectionChapterName(needUri[2], names); @@ -121,7 +121,7 @@ public List<CacheFile> updateChapterFileList(String collectionPath, List<CacheFi .setWholeVisibility(View.VISIBLE) .setCollectionPath(collectionPath) .setCollectionName(names[0]) - .setChapterPath(collectionPath + File.separator + chapterFile[i].getName()) + .setChapterPath(collectionPath + File.separator + chapterFile.get(i).getName()) .setChapterName(names[1]) .setAudioPath(needUri[0].toString()) .setVideoPath(needUri[1].toString()) @@ -161,11 +161,11 @@ public static List<CacheFile> collection2ChapterCacheFileList(Fragment fragment, //获取一个合集路径 collectionPath = collectionCacheFileList.get(n).getCollectionPath(); //获取一个合集下面所有的章节 - DocumentFile[] chapterFile = UriTool.getCollectionChapterFile(fragment, collectionPath); - for (int i = 0; i < chapterFile.length; i++) { + List<DocumentFile> chapterFile = UriTool.getCollectionChapterFile(fragment, collectionPath); + for (int i = 0; i < chapterFile.size(); i++) { //获取章节里需要的Uri - needUri = UriTool.getNeedUri(chapterFile[i], needUri); + needUri = UriTool.getNeedUri(chapterFile.get(i), needUri); //获取合集名称和章节名称 names = UriTool.getCollectionChapterName(needUri[2], names); @@ -176,7 +176,7 @@ public static List<CacheFile> collection2ChapterCacheFileList(Fragment fragment, .setWholeVisibility(View.VISIBLE) .setCollectionPath(collectionPath) .setCollectionName(names[0]) - .setChapterPath(collectionPath + File.separator + chapterFile[i].getName()) + .setChapterPath(collectionPath + File.separator + chapterFile.get(i).getName()) .setChapterName(names[1]) .setAudioPath(needUri[0].toString()) .setVideoPath(needUri[1].toString()) diff --git a/app/src/main/java/com/molihua/hlbmerge/utils/BiliDanmukuParserTools.java b/app/src/main/java/com/molihua/hlbmerge/utils/BiliDanmukuParserTools.java new file mode 100644 index 0000000..c813e00 --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/utils/BiliDanmukuParserTools.java @@ -0,0 +1,345 @@ +package com.molihua.hlbmerge.utils; + +import static master.flame.danmaku.danmaku.model.IDanmakus.ST_BY_TIME; + +import android.content.Context; +import android.graphics.Color; +import android.text.TextUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +import java.io.IOException; +import java.util.Locale; + +import master.flame.danmaku.danmaku.model.AlphaValue; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.Duration; +import master.flame.danmaku.danmaku.model.IDisplayer; +import master.flame.danmaku.danmaku.model.SpecialDanmaku; +import master.flame.danmaku.danmaku.model.android.DanmakuFactory; +import master.flame.danmaku.danmaku.model.android.Danmakus; +import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; +import master.flame.danmaku.danmaku.parser.android.AndroidFileSource; +import master.flame.danmaku.danmaku.util.DanmakuUtils; + + +/** + * @ClassName: BiliDanmukuParserTools + * @Author: molihuan + * @Date: 2022/12/29/20:25 + * @Description: xml弹幕解析类 + */ +public class BiliDanmukuParserTools extends BaseDanmakuParser { + + protected float mDispScaleX; + protected float mDispScaleY; + + //网络弹幕xml文件id + public String chatId; + + static {//指定包 + System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver"); + } + + public BiliDanmukuParserTools(Context context) { + } + + /** + * 获取网络弹幕xml文件id + * + * @return + */ + public String getChatId() { + return chatId; + } + + @Override + public Danmakus parse() { + + if (mDataSource != null) { + //获取弹幕文件数据源 + AndroidFileSource source = (AndroidFileSource) mDataSource; + try { + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + XmlContentHandler contentHandler = new XmlContentHandler(); + xmlReader.setContentHandler(contentHandler); + xmlReader.parse(new InputSource(source.data())); + return contentHandler.getResult(); + } catch (SAXException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + public class XmlContentHandler extends DefaultHandler { + + private static final String TRUE_STRING = "true"; + public Danmakus result; + public BaseDanmaku item = null; + public boolean completed = false; + public int index = 0; + public String localTag = new String(); + + public Danmakus getResult() { + return result; + } + + @Override + public void startDocument() throws SAXException { + result = new Danmakus(ST_BY_TIME, false, mContext.getBaseComparator()); + } + + @Override + public void endDocument() throws SAXException { + completed = true; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + String tagName = localName.length() != 0 ? localName : qName; + + //自己的解析---解析网络弹幕xml文件id + if (tagName.equals("chatid")) { + //保存标签名,方便接下来处理内容字符串。 + localTag = tagName; + } + + tagName = tagName.toLowerCase(Locale.getDefault()).trim(); + if (tagName.equals("d")) { + // <d p="23.826000213623,1,25,16777215,1422201084,0,057075e9,757076900">我从未见过如此厚颜无耻之猴</d> + // 0:时间(弹幕出现时间) + // 1:类型(1从右至左滚动弹幕|6从左至右滚动弹幕|5顶端固定弹幕|4底端固定弹幕|7高级弹幕|8脚本弹幕) + // 2:字号 + // 3:颜色 + // 4:时间戳 ? + // 5:弹幕池id + // 6:用户hash + // 7:弹幕id + String pValue = attributes.getValue("p"); + // parse p value to danmaku + String[] values = pValue.split(","); + if (values.length > 0) { + long time = (long) (parseFloat(values[0]) * 1000); // 出现时间 + int type = parseInteger(values[1]); // 弹幕类型 + float textSize = parseFloat(values[2]); // 字体大小 + int color = (int) ((0x00000000ff000000 | parseLong(values[3])) & 0x00000000ffffffff); // 颜色 + // int poolType = parseInteger(values[5]); // 弹幕池类型(忽略 + item = mContext.mDanmakuFactory.createDanmaku(type, mContext); + if (item != null) { + item.setTime(time); + item.textSize = textSize * (mDispDensity - 0.6f); + item.textColor = color; + item.textShadowColor = color <= Color.BLACK ? Color.WHITE : Color.BLACK; + } + } + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + //清空标签名 + if (localTag.equals("chatid")) { + localTag = ""; + } + + if (item != null && item.text != null) { + if (item.duration != null) { + String tagName = localName.length() != 0 ? localName : qName; + if (tagName.equalsIgnoreCase("d")) { + item.setTimer(mTimer); + item.flags = mContext.mGlobalFlagValues; + Object lock = result.obtainSynchronizer(); + synchronized (lock) { + result.addItem(item); + } + } + } + item = null; + } + } + + @Override + public void characters(char[] ch, int start, int length) { + //处理chatid内容字符串 + if (localTag.equals("chatid")) { + chatId = new String(ch, start, length); + } + + if (item != null) { + DanmakuUtils.fillText(item, decodeXmlString(new String(ch, start, length))); + item.index = index++; + + // initial specail danmaku data + String text = String.valueOf(item.text).trim(); + if (item.getType() == BaseDanmaku.TYPE_SPECIAL && text.startsWith("[") + && text.endsWith("]")) { + //text = text.substring(1, text.length() - 1); + String[] textArr = null;//text.split(",", -1); + try { + JSONArray jsonArray = new JSONArray(text); + textArr = new String[jsonArray.length()]; + for (int i = 0; i < textArr.length; i++) { + textArr[i] = jsonArray.getString(i); + } + } catch (JSONException e) { + e.printStackTrace(); + } + + if (textArr == null || textArr.length < 5 || TextUtils.isEmpty(textArr[4])) { + item = null; + return; + } + DanmakuUtils.fillText(item, textArr[4]); + float beginX = parseFloat(textArr[0]); + float beginY = parseFloat(textArr[1]); + float endX = beginX; + float endY = beginY; + String[] alphaArr = textArr[2].split("-"); + int beginAlpha = (int) (AlphaValue.MAX * parseFloat(alphaArr[0])); + int endAlpha = beginAlpha; + if (alphaArr.length > 1) { + endAlpha = (int) (AlphaValue.MAX * parseFloat(alphaArr[1])); + } + long alphaDuraion = (long) (parseFloat(textArr[3]) * 1000); + long translationDuration = alphaDuraion; + long translationStartDelay = 0; + float rotateY = 0, rotateZ = 0; + if (textArr.length >= 7) { + rotateZ = parseFloat(textArr[5]); + rotateY = parseFloat(textArr[6]); + } + if (textArr.length >= 11) { + endX = parseFloat(textArr[7]); + endY = parseFloat(textArr[8]); + if (!"".equals(textArr[9])) { + translationDuration = parseInteger(textArr[9]); + } + if (!"".equals(textArr[10])) { + translationStartDelay = (long) (parseFloat(textArr[10])); + } + } + if (isPercentageNumber(textArr[0])) { + beginX *= DanmakuFactory.BILI_PLAYER_WIDTH; + } + if (isPercentageNumber(textArr[1])) { + beginY *= DanmakuFactory.BILI_PLAYER_HEIGHT; + } + if (textArr.length >= 8 && isPercentageNumber(textArr[7])) { + endX *= DanmakuFactory.BILI_PLAYER_WIDTH; + } + if (textArr.length >= 9 && isPercentageNumber(textArr[8])) { + endY *= DanmakuFactory.BILI_PLAYER_HEIGHT; + } + item.duration = new Duration(alphaDuraion); + item.rotationZ = rotateZ; + item.rotationY = rotateY; + mContext.mDanmakuFactory.fillTranslationData(item, beginX, + beginY, endX, endY, translationDuration, translationStartDelay, mDispScaleX, mDispScaleY); + mContext.mDanmakuFactory.fillAlphaData(item, beginAlpha, endAlpha, alphaDuraion); + + if (textArr.length >= 12) { + // 是否有描边 + if (!TextUtils.isEmpty(textArr[11]) && TRUE_STRING.equalsIgnoreCase(textArr[11])) { + item.textShadowColor = Color.TRANSPARENT; + } + } + if (textArr.length >= 13) { + //TODO 字体 textArr[12] + } + if (textArr.length >= 14) { + // Linear.easeIn or Quadratic.easeOut + ((SpecialDanmaku) item).isQuadraticEaseOut = ("0".equals(textArr[13])); + } + if (textArr.length >= 15) { + // 路径数据 + if (!"".equals(textArr[14])) { + String motionPathString = textArr[14].substring(1); + if (!TextUtils.isEmpty(motionPathString)) { + String[] pointStrArray = motionPathString.split("L"); + if (pointStrArray.length > 0) { + float[][] points = new float[pointStrArray.length][2]; + for (int i = 0; i < pointStrArray.length; i++) { + String[] pointArray = pointStrArray[i].split(","); + if (pointArray.length >= 2) { + points[i][0] = parseFloat(pointArray[0]); + points[i][1] = parseFloat(pointArray[1]); + } + } + mContext.mDanmakuFactory.fillLinePathData(item, points, mDispScaleX, + mDispScaleY); + } + } + } + } + } + + } + } + + private String decodeXmlString(String title) { + if (title.contains("&")) { + title = title.replace("&", "&"); + } + if (title.contains(""")) { + title = title.replace(""", "\""); + } + if (title.contains(">")) { + title = title.replace(">", ">"); + } + if (title.contains("<")) { + title = title.replace("<", "<"); + } + return title; + } + + } + + private boolean isPercentageNumber(String number) { + //return number >= 0f && number <= 1f; + return number != null && number.contains("."); + } + + private float parseFloat(String floatStr) { + try { + return Float.parseFloat(floatStr); + } catch (NumberFormatException e) { + return 0.0f; + } + } + + private int parseInteger(String intStr) { + try { + return Integer.parseInt(intStr); + } catch (NumberFormatException e) { + return 0; + } + } + + private long parseLong(String longStr) { + try { + return Long.parseLong(longStr); + } catch (NumberFormatException e) { + return 0; + } + } + + @Override + public BaseDanmakuParser setDisplayer(IDisplayer disp) { + super.setDisplayer(disp); + mDispScaleX = mDispWidth / DanmakuFactory.BILI_PLAYER_WIDTH; + mDispScaleY = mDispHeight / DanmakuFactory.BILI_PLAYER_HEIGHT; + return this; + } +} diff --git a/app/src/main/java/com/molihua/hlbmerge/utils/FileTools.java b/app/src/main/java/com/molihua/hlbmerge/utils/FileTools.java index a1f1b67..8a7dde3 100644 --- a/app/src/main/java/com/molihua/hlbmerge/utils/FileTools.java +++ b/app/src/main/java/com/molihua/hlbmerge/utils/FileTools.java @@ -8,6 +8,7 @@ import org.json.JSONObject; import java.io.File; +import java.io.FileFilter; import java.util.UUID; /** @@ -17,6 +18,8 @@ * @Description: */ public class FileTools { + + /** * 获取所有的合集路径、获取一个合集下的所有章节路径 * @@ -33,7 +36,16 @@ public static String[] getCollectionChapterPath(String path) { public static File[] getCollectionChapterFile(String path) { File file = new File(path); - return file.listFiles(); + + return file.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + if (pathname.isDirectory()) { + return true; + } + return false; + } + }); } public static String[] getCollectionChapterName(byte[] jsonByte, String[] result) { diff --git a/app/src/main/java/com/molihua/hlbmerge/utils/GeneralTools.java b/app/src/main/java/com/molihua/hlbmerge/utils/GeneralTools.java new file mode 100644 index 0000000..2da7b14 --- /dev/null +++ b/app/src/main/java/com/molihua/hlbmerge/utils/GeneralTools.java @@ -0,0 +1,41 @@ +package com.molihua.hlbmerge.utils; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.widget.Toast; + + +/** + * @ClassName: GeneralTools + * @Author: molihuan + * @Date: 2022/12/27/22:07 + * @Description: 通用工具 + */ +public class GeneralTools { + /** + * 调用第三方浏览器打开网址 + * + * @param context + * @param url 要浏览的资源地址 + */ + public static void jumpBrowser(Context context, String url) { + final Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + // 注意此处的判断intent.resolveActivity()可以返回显示该Intent的Activity对应的组件名 + if (intent.resolveActivity(context.getPackageManager()) != null) { + context.startActivity(Intent.createChooser(intent, "请选择浏览器")); + } else { + Toast.makeText(context.getApplicationContext(), "请下载浏览器", Toast.LENGTH_SHORT).show(); + } + } + + /** + * 检查更新 + */ + public static void checkUpdata() { + + } + +} diff --git a/app/src/main/java/com/molihua/hlbmerge/utils/LConstants.java b/app/src/main/java/com/molihua/hlbmerge/utils/LConstants.java index 9c1bb43..b36dcaa 100644 --- a/app/src/main/java/com/molihua/hlbmerge/utils/LConstants.java +++ b/app/src/main/java/com/molihua/hlbmerge/utils/LConstants.java @@ -10,18 +10,18 @@ public class LConstants { //去除一些特殊的字符的正则表达式 /r去换行 public static final String SPECIAL_CHARACTERS_REGULAR_RULE = "[\t\r\n`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】\"‘;:”“’。, 、?-]*"; + //bilibili主页url + public static final String URL_BILIBILI_HOMEPAGE = "https://space.bilibili.com/454222981"; //************************** Fragment Tag标志 **************************** public static final String TAG_FRAGMENT_MAIN_TITLEBAR = "frameLayout_main_titlebar_area_mlh"; public static final String TAG_FRAGMENT_MAIN_HANDLE = "frameLayout_main_handle_area_mlh"; public static final String TAG_FRAGMENT_MAIN_FILE_SHOW = "frameLayout_main_file_show_area_mlh"; public static final String TAG_FRAGMENT_MAIN_FFMPEG = "frameLayout_main_ffmpeg_area_mlh"; - - - public static final String TAG_ACTIVITY_FRAGMENT = "framelayout_show_body_mlh"; - public static final String TAG_FRAGMENT_TABBAR = "frameLayout_tabbar_area_mlh"; - public static final String TAG_FRAGMENT_FILE_SHOW = "frameLayout_file_show_area_mlh"; - public static final String TAG_FRAGMENT_HANDLE = "frameLayout_handle_area_mlh"; - public static final String TAG_DIALOG_FRAGMENT = "framelayout_dialog_show_body_mlh"; + //************************** 更新url **************************** + //默认更新url(gitee) + public static final String DEFAULT_UPDATE_URL = "https://gitee.com/molihuan/BilibiliCacheVideoMergeAndroid/tree/master/jsonapi/update_release.json"; + //备份更新url(github) + public static final String BACKUPS_UPDATE_URL = "https://github.com/molihuan/BilibiliCacheVideoMerge/tree/master/jsonapi/update_release.json"; } diff --git a/app/src/main/java/com/molihua/hlbmerge/utils/RxFfmpegTools.java b/app/src/main/java/com/molihua/hlbmerge/utils/RxFfmpegTools.java index 8debc58..52948ce 100644 --- a/app/src/main/java/com/molihua/hlbmerge/utils/RxFfmpegTools.java +++ b/app/src/main/java/com/molihua/hlbmerge/utils/RxFfmpegTools.java @@ -12,7 +12,13 @@ */ public class RxFfmpegTools { - + /** + * 同步执行命令 + * + * @param cmdStr + * @param ffmpegCallback + * @return + */ public static int runCommand(String cmdStr, RxFFmpegCallback ffmpegCallback) { String[] cmd = cmdStr.split(" "); @@ -22,4 +28,11 @@ public static int runCommand(String cmdStr, RxFFmpegCallback ffmpegCallback) { .runCommand(cmd, ffmpegCallback); } + /** + * 退出命令执行 + */ + public static void exitRunCommand() { + RxFFmpegInvoke.getInstance().exit(); + } + } diff --git a/app/src/main/java/com/molihua/hlbmerge/utils/UriTool.java b/app/src/main/java/com/molihua/hlbmerge/utils/UriTool.java index e5b16b5..f9e70ae 100644 --- a/app/src/main/java/com/molihua/hlbmerge/utils/UriTool.java +++ b/app/src/main/java/com/molihua/hlbmerge/utils/UriTool.java @@ -2,18 +2,28 @@ import android.content.Context; import android.net.Uri; +import android.view.View; import androidx.annotation.NonNull; import androidx.documentfile.provider.DocumentFile; import androidx.fragment.app.Fragment; +import com.blankj.molihuan.utilcode.util.FileIOUtils; +import com.blankj.molihuan.utilcode.util.FileUtils; import com.blankj.molihuan.utilcode.util.UriUtils; +import com.molihua.hlbmerge.R; +import com.molihuan.pathselector.dialog.BaseDialog; +import com.molihuan.pathselector.dialog.impl.MessageDialog; +import com.molihuan.pathselector.entity.FontBean; import com.molihuan.pathselector.utils.PermissionsTools; import com.molihuan.pathselector.utils.UriTools; import com.xuexiang.xtask.XTask; import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction; import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog; +import java.io.File; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -24,6 +34,89 @@ */ public class UriTool { + public static String tempPath; + + /** + * 复制DocumentFile + * + * @param fragment + * @param srcParentPath + * @param srcPath + * @param destPath + */ + public static void copyDocumentFile(Fragment fragment, String srcParentPath, String srcPath, String destPath) { + + Context context = fragment.getContext(); + + Uri uri = UriTools.path2Uri(srcPath, false); + + String existsPermission = PermissionsTools.existsGrantedUriPermission(uri, fragment); + + if (existsPermission == null) { + + //没有权限申请权限 + XTask.postToMain(new Runnable() { + @Override + public void run() { + //申请权限弹窗 + new MessageDialog(context) + .setContent(new FontBean(String.format(context.getString(R.string.tip_uri_authorization_permission_content_hlb), srcPath))) + .setConfirm(new FontBean(context.getString(R.string.option_confirm_hlb), 15), new BaseDialog.IOnConfirmListener() { + @Override + public boolean onClick(View v, BaseDialog dialog) { + //申请权限 + PermissionsTools.goApplyUriPermissionPage(uri, fragment); + dialog.dismiss(); + return false; + } + }) + .setCancel(new FontBean(context.getString(R.string.option_cancel_hlb), 15), new BaseDialog.IOnCancelListener() { + @Override + public boolean onClick(View v, BaseDialog dialog) { + dialog.dismiss(); + return false; + } + }) + .show(); + } + }); + + return; + } + + Uri targetUri = Uri.parse(existsPermission + uri.toString().replaceFirst(UriTools.URI_PERMISSION_REQUEST_COMPLETE_PREFIX, "")); + + //Mtools.log(targetUri); + + DocumentFile rootDocumentFile = DocumentFile.fromSingleUri(context, targetUri); + Objects.requireNonNull(rootDocumentFile, "rootDocumentFile is null"); + + //创建一个 DocumentFile表示以给定的 Uri根的文档树。其实就是获取子目录的权限 + DocumentFile pickedDir = rootDocumentFile.fromTreeUri(context, targetUri); + Objects.requireNonNull(pickedDir, "pickedDir is null"); + + DocumentFile[] documentFiles = pickedDir.listFiles(); + + if (documentFiles != null) { + for (int i = 0; i < documentFiles.length; i++) { + + tempPath = srcPath + File.separator + documentFiles[i].getName(); + + if (documentFiles[i].isDirectory()) { +// LogUtils.w(documentFiles[i].getUri() + "\n夹夹夹夹夹夹夹夹夹夹" + tempPath.replace(srcParentPath, destPath)); + FileUtils.createOrExistsDir(tempPath.replace(srcParentPath, destPath)); + copyDocumentFile(fragment, srcParentPath, tempPath, destPath); + } else { +// LogUtils.w(documentFiles[i].getUri() + "\n文件文件文件文件文件" + tempPath.replace(srcParentPath, destPath)); + byte[] srcBytes = UriUtils.uri2Bytes(documentFiles[i].getUri()); + FileIOUtils.writeFileFromBytesByChannel(tempPath.replace(srcParentPath, destPath), srcBytes, true); + } + } + } + + + } + public static void grantedUriPermission(String path, Fragment fragment) { //获取上下文 Context context = fragment.getContext(); @@ -59,7 +152,7 @@ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) } } - public static DocumentFile[] getCollectionChapterFile(Fragment fragment, String currentPath) { + public static List<DocumentFile> getCollectionChapterFile(Fragment fragment, String currentPath) { //获取上下文 Context context = fragment.getContext(); Objects.requireNonNull(context, "context is null"); @@ -110,7 +203,17 @@ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) Objects.requireNonNull(pickedDir, "pickedDir is null"); DocumentFile[] documentFiles = pickedDir.listFiles(); - return documentFiles; + //TODO 去除不是文件夹的item + if (documentFiles == null) { + return null; + } + List<DocumentFile> fileList = new ArrayList<>(); + for (DocumentFile documentFile : documentFiles) { + if (documentFile.isDirectory()) { + fileList.add(documentFile); + } + } + return fileList; } public static Uri[] getNeedUri(DocumentFile chapterFile, Uri[] result) { diff --git a/app/src/main/res/drawable/bg_mm.png b/app/src/main/res/drawable/bg_mm.png deleted file mode 100644 index e504481..0000000 Binary files a/app/src/main/res/drawable/bg_mm.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_player_screen_lock.xml b/app/src/main/res/drawable/ic_player_screen_lock.xml new file mode 100644 index 0000000..7df88c1 --- /dev/null +++ b/app/src/main/res/drawable/ic_player_screen_lock.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:state_selected="true" android:drawable="@drawable/ic_player_screen_lock_close"/> + <item android:drawable="@drawable/ic_player_screen_lock_open"/> + +</selector> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_player_screen_lock_close.png b/app/src/main/res/drawable/ic_player_screen_lock_close.png new file mode 100644 index 0000000..d2ba809 Binary files /dev/null and b/app/src/main/res/drawable/ic_player_screen_lock_close.png differ diff --git a/app/src/main/res/drawable/ic_player_screen_lock_open.png b/app/src/main/res/drawable/ic_player_screen_lock_open.png new file mode 100644 index 0000000..b312ba3 Binary files /dev/null and b/app/src/main/res/drawable/ic_player_screen_lock_open.png differ diff --git a/app/src/main/res/drawable/ic_setting.png b/app/src/main/res/drawable/ic_setting.png new file mode 100644 index 0000000..790e32c Binary files /dev/null and b/app/src/main/res/drawable/ic_setting.png differ diff --git a/app/src/main/res/drawable/img_beautiful_scenery.png b/app/src/main/res/drawable/img_beautiful_scenery.png deleted file mode 100644 index c351c49..0000000 Binary files a/app/src/main/res/drawable/img_beautiful_scenery.png and /dev/null differ diff --git a/app/src/main/res/drawable/mm_tp_bmp.xml b/app/src/main/res/drawable/mm_tp_bmp.xml deleted file mode 100644 index 7798c10..0000000 --- a/app/src/main/res/drawable/mm_tp_bmp.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/bg_mm" - android:tileMode="disabled" android:gravity="center" > - -</bitmap> \ No newline at end of file diff --git a/app/src/main/res/drawable/style_common_shadow_bg.xml b/app/src/main/res/drawable/style_common_shadow_bg.xml new file mode 100644 index 0000000..27ffb2a --- /dev/null +++ b/app/src/main/res/drawable/style_common_shadow_bg.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape android:shape="oval" + xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/grey_transparent90_mlh"/> + <size + android:height="40dp" + android:width="40dp"/> +</shape> diff --git a/app/src/main/res/drawable/style_common_shadow_transition_right_bg.xml b/app/src/main/res/drawable/style_common_shadow_transition_right_bg.xml new file mode 100644 index 0000000..5f21394 --- /dev/null +++ b/app/src/main/res/drawable/style_common_shadow_transition_right_bg.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + + <gradient android:startColor="@color/grey900_mlh" + android:endColor="@android:color/transparent" + android:angle="180"/> + +</shape> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..4765e35 --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/titlebar_show_area" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <androidx.core.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:overScrollMode="never"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:paddingTop="50dp" + android:paddingBottom="25dp"> + + <androidx.appcompat.widget.AppCompatImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="Logo" + android:src="@drawable/ml130" /> + + <TextView + android:id="@+id/version" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="15dp" + android:textColor="@color/xui_config_color_gray_3" + android:textSize="16sp" /> + + <com.xuexiang.xui.widget.grouplist.XUIGroupListView + android:id="@+id/about_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="35dp" /> + + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <TextView + android:id="@+id/copyright" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="25dp" + android:gravity="center_horizontal" + android:textColor="@color/xui_config_color_gray_7" /> + + </LinearLayout> + + </androidx.core.widget.NestedScrollView> + + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_html.xml b/app/src/main/res/layout/activity_html.xml new file mode 100644 index 0000000..aaace16 --- /dev/null +++ b/app/src/main/res/layout/activity_html.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout android:layout_height="match_parent" + android:layout_width="match_parent" + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/titlebar_show_area" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <WebView + android:id="@+id/asstm" + android:layout_width="match_parent" + android:layout_height="match_parent" /> +</LinearLayout> + + diff --git a/app/src/main/res/layout/activity_play_video.xml b/app/src/main/res/layout/activity_play_video.xml new file mode 100644 index 0000000..a9d9a7e --- /dev/null +++ b/app/src/main/res/layout/activity_play_video.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout android:layout_height="match_parent" + android:layout_width="match_parent" + xmlns:android="http://schemas.android.com/apk/res/android"> + + <xyz.doikki.videoplayer.player.VideoView + android:id="@+id/play_video_view" + android:layout_width="match_parent" + android:layout_height="300dp" /> + + <Button + android:id="@+id/btn_copypath" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/play_video_view" + android:padding="15dp" + android:layout_margin="5dp" + android:textAllCaps="false" /> + + <Button + android:id="@+id/btn_updataxml" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/btn_copypath" + android:padding="15dp" + android:layout_margin="5dp" + android:text="更新本地弹幕" + android:textAllCaps="false" /> + + <!-- <androidx.core.widget.NestedScrollView--> + <!-- android:layout_width="match_parent"--> + <!-- android:layout_height="wrap_content"--> + <!-- android:overScrollMode="never"--> + <!-- android:layout_below="@id/btn_copypath">--> + + <!-- </androidx.core.widget.NestedScrollView>--> + + +</RelativeLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml new file mode 100644 index 0000000..b77d935 --- /dev/null +++ b/app/src/main/res/layout/activity_settings.xml @@ -0,0 +1,191 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/titlebar_show_area" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="缓存位置" + android:textColor="@color/orange_mlh" + android:paddingHorizontal="15dp" + android:paddingTop="15dp" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:descendantFocusability="blocksDescendants" + android:orientation="horizontal" + android:padding="15dp" + android:gravity="center_vertical"> + + <TextView + android:id="@+id/tv_cache_path_tip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="当前缓存路径:" + android:textColor="@color/black" + android:textSize="16dp" + android:layout_centerVertical="true" + android:layout_marginRight="15dp" /> + + <TextView + android:id="@+id/tv_cache_path_show" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/test_text" + android:textColor="@color/grey500_mlh" + android:textSize="16dp" + android:layout_toRightOf="@id/tv_cache_path_tip" + android:layout_centerVertical="true" /> + + </RelativeLayout> + + + <LinearLayout + android:id="@+id/line_switch_bilibili_app_version" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:descendantFocusability="blocksDescendants" + android:padding="15dp" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Bilibili软件(只支持官方包名)" + android:textColor="@color/black_mlh" + android:textSize="16dp" /> + + <com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner + android:id="@+id/ms_bilibili_version" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:ms_entries="@array/bilibili_app_version_string_array" /> + + </LinearLayout> + + + <RelativeLayout + android:id="@+id/rela_custom_cache_path" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:descendantFocusability="blocksDescendants" + android:orientation="horizontal" + android:padding="15dp" + android:gravity="center_vertical" + android:background="@drawable/style_click_rectangle_transp_gray_mlh"> + + <TextView + android:id="@+id/tv_custom_cache_path_tip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="自定义缓存文件位置" + android:textColor="@color/black" + android:textSize="16dp" /> + + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/tv_custom_cache_path_tip" + android:layout_marginRight="20dp" + android:text="当你选择了此项则Bilibili软件(只支持官方包名)选项失效" /> + + + </RelativeLayout> + + + <View + android:layout_width="match_parent" + android:layout_height="0.2dp" + android:background="@color/xui_btn_gray_normal_color" /> + + </LinearLayout> + + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="输出位置" + android:textColor="@color/orange_mlh" + android:paddingHorizontal="15dp" + android:paddingTop="15dp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:descendantFocusability="blocksDescendants" + android:orientation="horizontal" + android:padding="15dp" + android:gravity="center_vertical"> + + <TextView + android:id="@+id/tv_output_path_tip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="当前输出路径:" + android:textColor="@color/black" + android:textSize="16dp" + android:layout_centerVertical="true" + android:layout_marginRight="15dp" /> + + <TextView + android:id="@+id/tv_output_path_show" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/test_text" + android:textColor="@color/grey500_mlh" + android:textSize="16dp" + android:layout_toRightOf="@id/tv_output_path_tip" + android:layout_centerVertical="true" /> + + </RelativeLayout> + + + <RelativeLayout + android:id="@+id/rela_custom_output_path" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:descendantFocusability="blocksDescendants" + android:orientation="horizontal" + android:padding="15dp" + android:gravity="center_vertical" + android:background="@drawable/style_click_rectangle_transp_gray_mlh"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="自定义输出位置" + android:textColor="@color/black" + android:textSize="16dp" /> + + </RelativeLayout> + + <View + android:layout_width="match_parent" + android:layout_height="0.2dp" + android:background="@color/xui_btn_gray_normal_color" /> + + </LinearLayout> + + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_statement.xml b/app/src/main/res/layout/dialog_statement.xml new file mode 100644 index 0000000..81755b7 --- /dev/null +++ b/app/src/main/res/layout/dialog_statement.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/tx_statement" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:ignore="MissingConstraints" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_back_titlebar.xml b/app/src/main/res/layout/fragment_back_titlebar.xml new file mode 100644 index 0000000..ae46972 --- /dev/null +++ b/app/src/main/res/layout/fragment_back_titlebar.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="@color/orange_mlh"> + + <com.xuexiang.xui.widget.button.shadowbutton.ShadowImageView + android:id="@+id/btn_back_toolbar" + android:layout_width="30dp" + android:layout_height="30dp" + android:src="@drawable/ic_title_bar_back" + android:layout_centerVertical="true" + app:sb_shape_type="round" + android:layout_marginLeft="10dp" /> + + <RelativeLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_centerInParent="true"> + + <com.xuexiang.xui.widget.textview.autofit.AutoFitTextView + android:id="@+id/main_title_toolbar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/test_text" + android:textSize="21dp" + android:textColor="@color/white" /> + + <com.xuexiang.xui.widget.textview.autofit.AutoFitTextView + android:id="@+id/subtitle_toolbar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/main_title_toolbar" + android:layout_centerHorizontal="true" + android:textColor="@color/white" /> + + + </RelativeLayout> + + <TextView + android:id="@+id/tv_right_option" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/test_text" + android:textColor="@color/white_mlh" + android:layout_centerVertical="true" + android:layout_alignParentRight="true" + android:layout_marginRight="15dp" + android:background="@drawable/style_click_rectangle_transp_gray_mlh" + android:visibility="invisible" /> + + + </RelativeLayout> + + +</FrameLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dk_video_bottom_control.xml b/app/src/main/res/layout/fragment_dk_video_bottom_control.xml new file mode 100644 index 0000000..086f515 --- /dev/null +++ b/app/src/main/res/layout/fragment_dk_video_bottom_control.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <RelativeLayout + android:id="@+id/rela_bottom_control_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:clickable="true" + android:focusable="true" + android:padding="10dp" + android:background="@drawable/dkplayer_shape_stardard_controller_bottom_bg"> + + <ImageView + android:id="@+id/iv_play" + android:layout_width="22dp" + android:layout_height="22dp" + android:layout_centerVertical="true" + android:contentDescription="@null" + android:src="@drawable/ic_video_play" + android:background="@drawable/style_click_round_transp_gray_mlh" /> + + <ImageView + android:id="@+id/iv_barrage" + android:layout_width="22dp" + android:layout_height="22dp" + android:layout_centerVertical="true" + android:layout_marginLeft="10dp" + android:layout_toEndOf="@id/iv_play" + android:contentDescription="@null" + android:src="@drawable/ic_barrage_open" + android:background="@drawable/style_click_round_transp_gray_mlh" /> + + <TextView + android:id="@+id/curr_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginStart="10dp" + android:layout_toEndOf="@id/iv_barrage" + android:textColor="#ffffff" /> + + <SeekBar + android:id="@+id/sb_progress" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toStartOf="@id/total_time" + android:layout_toEndOf="@id/curr_time" /> + + <TextView + android:id="@+id/total_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginRight="10dp" + android:layout_toStartOf="@id/iv_full_screen" + android:textColor="#ffffff" /> + + + <ImageView + android:id="@+id/iv_full_screen" + android:layout_width="22dp" + android:layout_height="22dp" + android:layout_alignParentEnd="true" + android:layout_marginEnd="0dp" + android:src="@drawable/ic_video_view_biggest" + android:background="@drawable/style_click_round_transp_gray_mlh" /> + + </RelativeLayout> + + +</FrameLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dk_video_complete.xml b/app/src/main/res/layout/fragment_dk_video_complete.xml new file mode 100644 index 0000000..0bcd2b2 --- /dev/null +++ b/app/src/main/res/layout/fragment_dk_video_complete.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/complete_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#33000000"> + + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="vertical"> + + <ImageView + android:id="@+id/iv_replay" + android:layout_width="@dimen/dkplayer_play_btn_size" + android:layout_height="@dimen/dkplayer_play_btn_size" + android:layout_gravity="center" + android:background="@drawable/dkplayer_shape_back_bg" + android:padding="@dimen/dkplayer_controller_icon_padding" + android:src="@drawable/dkplayer_ic_action_replay" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/dkplayer_replay" + android:layout_marginTop="12dp" + android:textColor="@android:color/white" /> + + </LinearLayout> +</FrameLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dk_video_controller.xml b/app/src/main/res/layout/fragment_dk_video_controller.xml new file mode 100644 index 0000000..0a1abf1 --- /dev/null +++ b/app/src/main/res/layout/fragment_dk_video_controller.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/controller_view" + android:background="@android:color/transparent"> + + + <ImageView + android:id="@+id/iv_lock" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_centerVertical="true" + android:layout_marginStart="24dp" + android:layout_marginTop="24dp" + android:layout_marginEnd="24dp" + android:layout_marginBottom="24dp" + android:background="@drawable/style_common_shadow_bg" + android:padding="10dp" + android:src="@drawable/ic_player_screen_lock" + android:visibility="gone" + tools:visibility="visible" /> + + +</RelativeLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dk_video_danmaku.xml b/app/src/main/res/layout/fragment_dk_video_danmaku.xml new file mode 100644 index 0000000..19cb0df --- /dev/null +++ b/app/src/main/res/layout/fragment_dk_video_danmaku.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <master.flame.danmaku.ui.widget.DanmakuView + android:id="@+id/danmaku_show_area" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_above="@id/rela_bottom_control_panel" /> + + +</FrameLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dk_video_setting.xml b/app/src/main/res/layout/fragment_dk_video_setting.xml new file mode 100644 index 0000000..2b377e2 --- /dev/null +++ b/app/src/main/res/layout/fragment_dk_video_setting.xml @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + <View + android:id="@+id/empty_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginRight="200dp" /> + + + <com.xuexiang.xui.widget.layout.ExpandableLayout + android:id="@+id/el_setting" + android:layout_width="200dp" + android:layout_height="match_parent" + android:orientation="horizontal" + android:layout_gravity="end" + app:el_duration="1000" + app:el_expanded="false"> + + <LinearLayout + android:id="@+id/linl_setting" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="#BF000000" + android:padding="10dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="弹幕设置" + android:textColor="@color/grey500_mlh" + android:textSize="15sp" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="弹幕字号" + android:textColor="@color/white_mlh" + android:layout_gravity="center_vertical" /> + + <com.xuexiang.xui.widget.picker.XSeekBar + android:id="@+id/xsb_danmaku_size" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="不透明度" + android:textColor="@color/white_mlh" + android:layout_gravity="center_vertical" /> + + <com.xuexiang.xui.widget.picker.XSeekBar + android:id="@+id/xsb_danmaku_alpha" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="弹幕速度" + android:textColor="@color/white_mlh" + android:layout_gravity="center_vertical" /> + + <com.xuexiang.xui.widget.picker.XSeekBar + android:id="@+id/xsb_danmaku_speed" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:visibility="gone"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="显示区域" + android:textColor="@color/white_mlh" + android:layout_gravity="center_vertical" /> + + <com.xuexiang.xui.widget.picker.XSeekBar + android:id="@+id/xsb_danmaku_show_area" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + </LinearLayout> + + + </LinearLayout> + + + </com.xuexiang.xui.widget.layout.ExpandableLayout> + + +</FrameLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dk_video_title.xml b/app/src/main/res/layout/fragment_dk_video_title.xml new file mode 100644 index 0000000..6da9209 --- /dev/null +++ b/app/src/main/res/layout/fragment_dk_video_title.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <RelativeLayout + android:id="@+id/rela_titlebar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:padding="5dp" + android:background="@drawable/dkplayer_shape_standard_controller_top_bg"> + + <ImageView + android:id="@+id/back" + android:layout_width="25dp" + android:layout_height="25dp" + android:layout_centerVertical="true" + android:contentDescription="@null" + android:src="@drawable/ic_title_bar_back" + android:background="@drawable/style_click_round_transp_gray_mlh" /> + + + <com.xuexiang.xui.widget.textview.MarqueeTextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toLeftOf="@id/iv_battery" + android:layout_centerVertical="true" + android:textColor="@color/white" + android:layout_toRightOf="@id/back" + android:layout_marginLeft="15dp" + android:singleLine="true" + tools:text="标题" /> + + + <ImageView + android:id="@+id/iv_battery" + android:layout_width="25dp" + android:layout_height="25dp" + android:src="@drawable/dkplayer_battery_level" + android:layout_toLeftOf="@id/sys_time" + android:layout_centerVertical="true" /> + + <TextView + android:id="@+id/sys_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@android:color/white" + tools:text="12:00" + android:layout_toLeftOf="@id/iv_setting" + android:layout_centerVertical="true" /> + + <ImageView + android:id="@+id/iv_setting" + android:layout_width="25dp" + android:layout_height="25dp" + android:layout_marginEnd="10dp" + android:layout_marginRight="10dp" + android:src="@drawable/ic_setting" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:background="@drawable/style_click_round_transp_gray_mlh" /> + </RelativeLayout> + +</FrameLayout> \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index eca70cf..603a94b 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> - <background android:drawable="@drawable/ic_launcher_background" /> - <foreground android:drawable="@drawable/ic_launcher_foreground" /> +<!-- <background android:drawable="@drawable/ic_launcher_background" />--> + <foreground android:drawable="@drawable/ml130" /> </adaptive-icon> \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 6580777..d231a56 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,6 +1,6 @@ -<resources xmlns:tools="http://schemas.android.com/tools"> +<resources> <!-- Base application theme. --> - <style name="Theme.Hlbmerge2" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> + <style name="Theme.Hlbmerge" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <!-- Primary brand color. --> <item name="colorPrimary">@color/purple_200</item> <item name="colorPrimaryVariant">@color/purple_700</item> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 51b2eb0..e5c8c62 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,8 +1,18 @@ <resources> - <string name="app_name">hlbmerge2</string> + <string name="app_name">HLB站缓存合并</string> <string-array name="judge_merge_dialog_export_type"> <item>有声音视频</item> <item>无声音视频</item> <item>仅音频</item> </string-array> + <string-array name="bilibili_app_version_string_array"> + <item>哔哩哔哩(国内版)</item> + <item>bilibili(国际版)</item> + <item>哔哩哔哩HD(平板)</item> + <item>哔哩哔哩(概念版)</item> + <item> </item> + </string-array> + <string name="tip_uri_authorization_permission_content_hlb">访问%s目录需要授予权限,\n是否进行授权?</string> + <string name="option_confirm_hlb">确定</string> + <string name="option_cancel_hlb">取消</string> </resources> \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 6fc7dfb..e45fb73 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,6 +1,6 @@ -<resources xmlns:tools="http://schemas.android.com/tools"> +<resources> <!-- Base application theme. --> - <style name="Theme.Hlbmerge2" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> + <style name="Theme.Hlbmerge" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <!-- Primary brand color. --> <item name="colorPrimary">@color/purple_500</item> <item name="colorPrimaryVariant">@color/purple_700</item> diff --git a/jsonapi/update_custom.json b/jsonapi/update_custom.json new file mode 100644 index 0000000..7fe84b8 --- /dev/null +++ b/jsonapi/update_custom.json @@ -0,0 +1,9 @@ +{ + "hasUpdate": true, + "isIgnorable": true, + "versionCode": 3, + "versionName": "1.0.2", + "updateLog": "\r\n1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。", + "apkUrl": "https://xuexiangjys.oss-cn-shanghai.aliyuncs.com/apk/xupdate_demo_1.0.2.apk", + "apkSize": 4096 +} \ No newline at end of file diff --git a/jsonapi/update_forced.json b/jsonapi/update_forced.json new file mode 100644 index 0000000..fb00465 --- /dev/null +++ b/jsonapi/update_forced.json @@ -0,0 +1,12 @@ +{ + "Code": 0, + "Msg": "", + "UpdateStatus": 2, + "VersionCode": 3, + "VersionName": "1.0.2", + "UploadTime": "2018-07-10 17:28:41", + "ModifyContent": "\r\n1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。", + "DownloadUrl": "https://xuexiangjys.oss-cn-shanghai.aliyuncs.com/apk/xupdate_demo_1.0.2.apk", + "ApkSize": 4096, + "ApkMd5": "E4B79A36EFB9F17DF7E3BB161F9BCFD8" +} \ No newline at end of file diff --git a/jsonapi/update_ignore.json b/jsonapi/update_ignore.json new file mode 100644 index 0000000..5eddea5 --- /dev/null +++ b/jsonapi/update_ignore.json @@ -0,0 +1,12 @@ +{ + "Code": 0, + "Msg": "", + "UpdateStatus": 3, + "VersionCode": 3, + "VersionName": "1.0.2", + "UploadTime": "2018-07-10 17:28:41", + "ModifyContent": "\r\n1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。", + "DownloadUrl": "https://xuexiangjys.oss-cn-shanghai.aliyuncs.com/apk/xupdate_demo_1.0.2.apk", + "ApkSize": 4096, + "ApkMd5": "E4B79A36EFB9F17DF7E3BB161F9BCFD8" +} \ No newline at end of file diff --git a/jsonapi/update_release.json b/jsonapi/update_release.json new file mode 100644 index 0000000..6655537 --- /dev/null +++ b/jsonapi/update_release.json @@ -0,0 +1,12 @@ +{ + "Code": 0, + "Msg": "", + "UpdateStatus": 1, + "VersionCode": 50, + "VersionName": "1.6.3", + "UploadTime": "2018-07-10 17:28:41", + "ModifyContent": "\r\n1、优化api接口666。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。", + "DownloadUrl": "https://xuexiangjys.oss-cn-shanghai.aliyuncs.com/apk/xupdate_demo_1.0.2.apk", + "ApkSize": 2048, + "ApkMd5": "" +} \ No newline at end of file