From f9a83e743663b19c3a91d0441cefc13a8c94078c Mon Sep 17 00:00:00 2001 From: SkyD666 <1161046314@qq.com> Date: Fri, 14 Jan 2022 21:54:09 +0800 Subject: [PATCH] =?UTF-8?q?[feature|optimize|fix]=E6=95=B4=E7=90=86?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E6=9D=83=E9=99=90=E4=BB=A3=E7=A0=81=EF=BC=9B?= =?UTF-8?q?=E6=95=B4=E7=90=86=E9=83=A8=E5=88=86=E6=8A=95=E5=B1=8F=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=9B=E6=94=AF=E6=8C=81=E8=B0=83=E8=8A=82=E5=BC=B9?= =?UTF-8?q?=E5=B9=95=E5=AD=97=E5=8F=B7=EF=BC=9B=E4=BC=98=E5=8C=96=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E7=95=8C=E9=9D=A2Toolbar=E6=98=BE=E7=A4=BA=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E4=BB=A3=E7=A0=81=EF=BC=9B=E5=AF=BC=E5=85=A5=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=BA=90=E5=89=8D=E7=94=B3=E8=AF=B7=E5=AD=98=E5=82=A8?= =?UTF-8?q?=E6=9D=83=E9=99=90=EF=BC=8C=E9=81=BF=E5=85=8D=E5=9C=A8=E6=9F=90?= =?UTF-8?q?=E4=BA=9B=E6=89=8B=E6=9C=BA=E4=B8=8A=E5=B4=A9=E6=BA=83=EF=BC=9B?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=95=B0=E6=8D=AE=E6=BA=90=E5=BC=B9=E5=87=BA?= =?UTF-8?q?Toast=E6=97=B6=E5=B4=A9=E6=BA=83=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=88=E6=B2=A1=E6=9C=89=E6=8A=8Acom.skyd.imomoe.util.ToastK?= =?UTF-8?q?t=E5=8A=A0=E5=85=A5=E6=B7=B7=E6=B7=86=E8=A7=84=E5=88=99?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 7 +- README.md | 19 +- app/proguard-rules.pro | 1 + .../skyd/imomoe/model/DataSourceManager.kt | 10 +- .../skyd/imomoe/net/{DoH.kt => DnsServer.kt} | 2 +- .../main/java/com/skyd/imomoe/net/Okhttp.kt | 2 +- .../main/java/com/skyd/imomoe/util/Number.kt | 12 + .../java/com/skyd/imomoe/util/Permission.kt | 18 + .../com/skyd/imomoe/util/dlna/CastObject.java | 201 ----------- .../com/skyd/imomoe/util/dlna/CastObject.kt | 90 +++++ .../imomoe/util/dlna/dmc/DLNACastService.java | 58 --- .../imomoe/util/dlna/dmc/DLNACastService.kt | 52 +++ .../com/skyd/imomoe/util/dlna/dmc/ICast.java | 40 --- .../com/skyd/imomoe/util/dlna/dmc/ICast.kt | 28 ++ .../skyd/imomoe/util/dlna/dmc/ILogger.java | 77 ---- .../com/skyd/imomoe/util/dlna/dmc/ILogger.kt | 56 +++ .../dlna/dmc/OnDeviceRegistryListener.java | 14 - .../util/dlna/dmc/OnDeviceRegistryListener.kt | 12 + ...Servlet.java => ContentResourceServlet.kt} | 35 +- .../util/dlna/dms/IMediaContentDao.java | 20 -- .../imomoe/util/dlna/dms/IMediaContentDao.kt | 10 + .../imomoe/util/dlna/dms/IResourceServer.java | 7 - .../imomoe/util/dlna/dms/IResourceServer.kt | 6 + .../util/dlna/dms/IResourceServerFactory.java | 30 -- .../util/dlna/dms/IResourceServerFactory.kt | 17 + .../imomoe/util/dlna/dms/MediaContentDao.kt | 4 + .../util/downloadanime/AnimeDownloadHelper.kt | 45 +-- .../view/activity/AnimeDetailActivity.kt | 8 +- .../view/activity/AnimeDownloadActivity.kt | 30 +- .../view/activity/ConfigDataSourceActivity.kt | 34 +- .../skyd/imomoe/view/activity/PlayActivity.kt | 71 ++-- .../imomoe/view/activity/SettingActivity.kt | 3 +- .../imomoe/view/component/AnimeToolbar.kt | 11 +- .../view/component/player/AnimeVideoPlayer.kt | 69 +++- .../component/player/DanmakuVideoPlayer.kt | 52 ++- .../player/DetailPlayerActivity.java | 338 ------------------ .../component/player/DetailPlayerActivity.kt | 224 ++++++++++++ .../danmaku/bili/BiliBiliDanmakuParser.kt | 40 ++- .../skyd/imomoe/view/fragment/HomeFragment.kt | 22 +- .../listener/dsl/OnPermissionsCallback.kt | 63 ++++ .../main/res/layout/activity_anime_detail.xml | 9 +- app/src/main/res/layout/activity_favorite.xml | 3 +- .../layout/layout_anime_video_player_land.xml | 210 ++++++----- app/src/main/res/values/strings.xml | 1 + doc/customdatasource/README.md | 17 +- 45 files changed, 983 insertions(+), 1095 deletions(-) rename app/src/main/java/com/skyd/imomoe/net/{DoH.kt => DnsServer.kt} (99%) create mode 100644 app/src/main/java/com/skyd/imomoe/util/Number.kt create mode 100644 app/src/main/java/com/skyd/imomoe/util/Permission.kt delete mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/CastObject.java create mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/CastObject.kt delete mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dmc/DLNACastService.java create mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dmc/DLNACastService.kt delete mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ICast.java create mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ICast.kt delete mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ILogger.java create mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ILogger.kt delete mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dmc/OnDeviceRegistryListener.java create mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dmc/OnDeviceRegistryListener.kt rename app/src/main/java/com/skyd/imomoe/util/dlna/dms/{ContentResourceServlet.java => ContentResourceServlet.kt} (60%) delete mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dms/IMediaContentDao.java create mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dms/IMediaContentDao.kt delete mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServer.java create mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServer.kt delete mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServerFactory.java create mode 100644 app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServerFactory.kt delete mode 100644 app/src/main/java/com/skyd/imomoe/view/component/player/DetailPlayerActivity.java create mode 100644 app/src/main/java/com/skyd/imomoe/view/component/player/DetailPlayerActivity.kt create mode 100644 app/src/main/java/com/skyd/imomoe/view/listener/dsl/OnPermissionsCallback.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index b76fc880..88c68f5e 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -13,13 +13,14 @@ + - - + + @@ -44,7 +45,7 @@ - + diff --git a/README.md b/README.md index 50998aa0..0a3592e4 100644 --- a/README.md +++ b/README.md @@ -42,14 +42,15 @@ 4. 支持**双指缩放**、**移动**、**旋转**视频 5. 支持视频**投屏**到电视 6. 支持部分视频**显示**、**发送弹幕**(需要数据源支持弹幕) -7. 支持**缓存视频**到本地(暂不支持m3u8格式资源缓存) -8. 支持**追番**(数据保存在本地) -9. 支持显示**观看历史**记录 -10. 支持显示**搜索历史**记录 -11. 支持改变视频**播放速度** -12. 支持改变**视频**显示**比例**(16:9, 4:3, 全屏等) -13. [支持**自定义**显示**数据源**](doc/customdatasource/README.md) -14. ...... +7. 支持输入某站弹幕链接播放网络弹幕(例如https://api.bilibili.com/x/v1/dm/list.so?oid=97495910) +8. 支持**缓存视频**到本地(暂不支持m3u8格式资源缓存) +9. 支持**追番**(数据保存在本地) +10. 支持显示**观看历史**记录 +11. 支持显示**搜索历史**记录 +12. 支持改变视频**播放速度** +13. 支持改变**视频**显示**比例**(16:9, 4:3, 全屏等) +14. [支持**自定义**显示**数据源**](doc/customdatasource/README.md) +15. ...... ## 运行截图 @@ -67,7 +68,7 @@ ## 安全说明 -**请勿**私自**传播APK**安装包,Github仓库为唯一长期仓库,**请仅在Github仓库下载安装包**,请勿下载来历不明的应用与Jar包,谨防隐私泄露,谨防受骗! +**请勿**私自**传播APK**安装包,Github仓库为唯一长期仓库,**请仅在Github仓库下载安装包**,请勿下载来历不明的应用与ads包,谨防隐私泄露,谨防受骗! ### 已发现未知来源的APK diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ca584616..a0fe8b79 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -105,6 +105,7 @@ public static final int *; -keep class com.skyd.imomoe.model.util.** { *; } -keep class com.skyd.imomoe.util.html.source.** { *; } -keep class com.skyd.imomoe.util.eventbus.** { *; } +-keep class com.skyd.imomoe.util.ToastKt { *; } # 与自定义数据源相关的库不应该被混淆 -keep class org.jsoup.** { *; } -keep class org.greenrobot.eventbus.** { *; } diff --git a/app/src/main/java/com/skyd/imomoe/model/DataSourceManager.kt b/app/src/main/java/com/skyd/imomoe/model/DataSourceManager.kt index 031f0aeb..9d839751 100644 --- a/app/src/main/java/com/skyd/imomoe/model/DataSourceManager.kt +++ b/app/src/main/java/com/skyd/imomoe/model/DataSourceManager.kt @@ -7,7 +7,6 @@ import com.skyd.imomoe.BuildConfig import com.skyd.imomoe.model.interfaces.IConst import com.skyd.imomoe.model.interfaces.IRouteProcessor import com.skyd.imomoe.model.interfaces.IUtil -import com.skyd.imomoe.util.showToast import com.skyd.imomoe.util.editor import com.skyd.imomoe.util.sharedPreferences import dalvik.system.DexClassLoader @@ -15,13 +14,6 @@ import java.io.File object DataSourceManager { -// var useCustomDataSource: Boolean -// get() { -// return App.context.sharedPreferences().getBoolean("useCustomDataSource", false) -// } -// set(value) { -// App.context.sharedPreferences().editor { putBoolean("useCustomDataSource", value) } -// } const val DEFAULT_DATA_SOURCE = "" @@ -97,7 +89,7 @@ object DataSourceManager { @Suppress("UNCHECKED_CAST") fun create(clazz: Class): T? { // 如果不使用自定义数据,直接返回null - if (dataSourceName == DEFAULT_DATA_SOURCE) return null + if (dataSourceName == DEFAULT_DATA_SOURCE && !BuildConfig.DEBUG) return null cache[clazz]?.let { return it.newInstance() as T } diff --git a/app/src/main/java/com/skyd/imomoe/net/DoH.kt b/app/src/main/java/com/skyd/imomoe/net/DnsServer.kt similarity index 99% rename from app/src/main/java/com/skyd/imomoe/net/DoH.kt rename to app/src/main/java/com/skyd/imomoe/net/DnsServer.kt index 5aeb9566..22be14d3 100644 --- a/app/src/main/java/com/skyd/imomoe/net/DoH.kt +++ b/app/src/main/java/com/skyd/imomoe/net/DnsServer.kt @@ -12,7 +12,7 @@ import com.skyd.imomoe.util.showToast import okhttp3.HttpUrl.Companion.toHttpUrl import java.lang.Exception -object DoH { +object DnsServer { val defaultDnsServer = hashMapOf( 0 to "", 1 to "https://223.5.5.5/dns-query", diff --git a/app/src/main/java/com/skyd/imomoe/net/Okhttp.kt b/app/src/main/java/com/skyd/imomoe/net/Okhttp.kt index 13189193..c44021e3 100644 --- a/app/src/main/java/com/skyd/imomoe/net/Okhttp.kt +++ b/app/src/main/java/com/skyd/imomoe/net/Okhttp.kt @@ -11,7 +11,7 @@ import java.io.File private val okhttpCache = Cache(File("cacheDir", "okhttpcache"), 10 * 1024 * 1024) private val bootstrapClient = OkHttpClient.Builder().cache(okhttpCache).build() -var dns: DnsOverHttps? = DoH.dnsServer.let { +var dns: DnsOverHttps? = DnsServer.dnsServer.let { if (it.isNullOrBlank()) null else { try { DnsOverHttps.Builder().client(bootstrapClient) diff --git a/app/src/main/java/com/skyd/imomoe/util/Number.kt b/app/src/main/java/com/skyd/imomoe/util/Number.kt new file mode 100644 index 00000000..ea96f78a --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/util/Number.kt @@ -0,0 +1,12 @@ +package com.skyd.imomoe.util + +/** + * 只拼接百分号% + */ +inline val Int.percentage: String + get() = "${this}%" + +/** + * 乘100后拼接百分号 + */ +fun Int.toPercentage(): String = "${this * 100}%" \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/util/Permission.kt b/app/src/main/java/com/skyd/imomoe/util/Permission.kt new file mode 100644 index 00000000..67a6451a --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/util/Permission.kt @@ -0,0 +1,18 @@ +package com.skyd.imomoe.util + +import android.app.Activity +import androidx.fragment.app.Fragment +import com.hjq.permissions.Permission +import com.hjq.permissions.XXPermissions +import com.skyd.imomoe.view.listener.dsl.OnSinglePermissionCallback +import com.skyd.imomoe.view.listener.dsl.requestSinglePermission + +fun Activity.requestManageExternalStorage(init: OnSinglePermissionCallback.() -> Unit) { + XXPermissions.with(this).permission(Permission.MANAGE_EXTERNAL_STORAGE) + .requestSinglePermission(init) +} + +fun Fragment.requestManageExternalStorage(init: OnSinglePermissionCallback.() -> Unit) { + XXPermissions.with(this).permission(Permission.MANAGE_EXTERNAL_STORAGE) + .requestSinglePermission(init) +} diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/CastObject.java b/app/src/main/java/com/skyd/imomoe/util/dlna/CastObject.java deleted file mode 100644 index dd033c15..00000000 --- a/app/src/main/java/com/skyd/imomoe/util/dlna/CastObject.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.skyd.imomoe.util.dlna; - -import androidx.annotation.NonNull; - -import com.skyd.imomoe.util.dlna.dmc.ICast; - -public class CastObject { - private CastObject() { - } - - public static ICast newInstance(String url, String id, String name) { - if (url.endsWith(".mp4")) { - return CastVideo.newInstance(url, id, name); - } else if (url.endsWith(".mp3")) { - return CastAudio.newInstance(url, id, name); - } else if (url.endsWith(".jpg")) { - return CastImage.newInstance(url, id, name); - } else { - return null; - } - } - - /** - * - */ - public static class CastAudio implements ICast.ICastVideo { - public static CastAudio newInstance(String url, String id, String name) { - return new CastAudio(url, id, name); - } - - public final String url; - - public final String id; - - public final String name; - - private long duration; - - public CastAudio(String url, String id, String name) { - this.url = url; - this.id = id; - this.name = name; - } - - /** - * @param duration the total time of video (ms) - */ - public CastAudio setDuration(long duration) { - this.duration = duration; - return this; - } - - @NonNull - @Override - public String getId() { - return id; - } - - @NonNull - @Override - public String getUri() { - return url; - } - - @Override - public String getName() { - return name; - } - - @Override - public long getDurationMillSeconds() { - return duration; - } - - @Override - public long getSize() { - return 0; - } - - @Override - public long getBitrate() { - return 0; - } - } - - /** - * - */ - public static class CastImage implements ICast.ICastVideo { - public static CastImage newInstance(String url, String id, String name) { - return new CastImage(url, id, name); - } - - public final String url; - - public final String id; - - public final String name; - - public CastImage(String url, String id, String name) { - this.url = url; - this.id = id; - this.name = name; - } - - @NonNull - @Override - public String getId() { - return id; - } - - @NonNull - @Override - public String getUri() { - return url; - } - - @Override - public String getName() { - return name; - } - - @Override - public long getDurationMillSeconds() { - return -1L; - } - - @Override - public long getSize() { - return 0; - } - - @Override - public long getBitrate() { - return 0; - } - } - - /** - * - */ - public static class CastVideo implements ICast.ICastVideo { - public static CastVideo newInstance(String url, String id, String name) { - return new CastVideo(url, id, name); - } - - public final String url; - - public final String id; - - public final String name; - - private long duration; - - public CastVideo(String url, String id, String name) { - this.url = url; - this.id = id; - this.name = name; - } - - /** - * @param duration the total time of video (ms) - */ - public CastVideo setDuration(long duration) { - this.duration = duration; - return this; - } - - @NonNull - @Override - public String getId() { - return id; - } - - @NonNull - @Override - public String getUri() { - return url; - } - - @Override - public String getName() { - return name; - } - - @Override - public long getDurationMillSeconds() { - return duration; - } - - @Override - public long getSize() { - return 0; - } - - @Override - public long getBitrate() { - return 0; - } - } -} diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/CastObject.kt b/app/src/main/java/com/skyd/imomoe/util/dlna/CastObject.kt new file mode 100644 index 00000000..bec1d17a --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/util/dlna/CastObject.kt @@ -0,0 +1,90 @@ +package com.skyd.imomoe.util.dlna + +import com.skyd.imomoe.util.dlna.dmc.ICast +import com.skyd.imomoe.util.dlna.dmc.ICast.ICastVideo + +object CastObject { + fun newInstance(url: String, id: String, name: String?): ICast? { + return when { + url.endsWith(".mp4") -> CastVideo.newInstance(url, id, name) + url.endsWith(".mp3") -> CastAudio.newInstance(url, id, name) + url.endsWith(".jpg") -> CastImage.newInstance(url, id, name) + else -> null + } + } + + class CastAudio( + override val uri: String, + override val id: String, + override val name: String? + ) : ICastVideo { + override var durationMillSeconds: Long = 0 + private set + + /** + * @param duration the total time of video (ms) + */ + fun setDuration(duration: Long): CastAudio { + durationMillSeconds = duration + return this + } + + override val size: Long + get() = 0 + override val bitrate: Long + get() = 0 + + companion object { + @JvmStatic + fun newInstance(url: String, id: String, name: String?): CastAudio { + return CastAudio(url, id, name) + } + } + } + + class CastImage( + override val uri: String, + override val id: String, + override val name: String? + ) : ICastVideo { + override val durationMillSeconds: Long + get() = -1L + override val size: Long + get() = 0 + override val bitrate: Long + get() = 0 + + companion object { + @JvmStatic + fun newInstance(url: String, id: String, name: String?): CastImage { + return CastImage(url, id, name) + } + } + } + + class CastVideo(override val uri: String, override val id: String, override val name: String?) : + ICastVideo { + override var durationMillSeconds: Long = 0 + private set + + /** + * @param duration the total time of video (ms) + */ + fun setDuration(duration: Long): CastVideo { + durationMillSeconds = duration + return this + } + + override val size: Long + get() = 0 + override val bitrate: Long + get() = 0 + + companion object { + @JvmStatic + fun newInstance(url: String, id: String, name: String?): CastVideo { + return CastVideo(url, id, name) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/DLNACastService.java b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/DLNACastService.java deleted file mode 100644 index 778e0113..00000000 --- a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/DLNACastService.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.skyd.imomoe.util.dlna.dmc; - -import android.content.Intent; - -import org.fourthline.cling.UpnpServiceConfiguration; -import org.fourthline.cling.android.AndroidUpnpServiceConfiguration; -import org.fourthline.cling.android.AndroidUpnpServiceImpl; -import org.fourthline.cling.android.FixedAndroidLogHandler; - -/** - * - */ -public class DLNACastService extends AndroidUpnpServiceImpl { - private final ILogger mLogger = new ILogger.DefaultLoggerImpl(this); - - @Override - public void onCreate() { - mLogger.i(String.format("[%s] onCreate", getClass().getName())); - org.seamless.util.logging.LoggingUtil.resetRootHandler(new FixedAndroidLogHandler()); - super.onCreate(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - mLogger.i(String.format("[%s] onStartCommand: %s , %s", getClass().getName(), intent, flags)); - return super.onStartCommand(intent, flags, startId); - } - - @Override - public void onDestroy() { - mLogger.w(String.format("[%s] onDestroy", getClass().getName())); - super.onDestroy(); - } - - @Override - protected UpnpServiceConfiguration createConfiguration() { - return new DLNACastServiceConfiguration(); - } - - // ---------------------------------------------------------------- - // ---- configuration - // ---------------------------------------------------------------- - private static final class DLNACastServiceConfiguration extends AndroidUpnpServiceConfiguration { - - @Override - public int getRegistryMaintenanceIntervalMillis() { - return 5000; //default is 3000! - } - - // @Override - // public ServiceType[] getExclusiveServiceTypes() { - // return new ServiceType[]{ - // DLNACastManager.SERVICE_RENDERING_CONTROL, - // DLNACastManager.SERVICE_AV_TRANSPORT, - // DLNACastManager.SERVICE_CONNECTION_MANAGER}; - // } - } -} diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/DLNACastService.kt b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/DLNACastService.kt new file mode 100644 index 00000000..b9637564 --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/DLNACastService.kt @@ -0,0 +1,52 @@ +package com.skyd.imomoe.util.dlna.dmc + +import org.fourthline.cling.android.AndroidUpnpServiceImpl +import com.skyd.imomoe.util.dlna.dmc.ILogger.DefaultLoggerImpl +import org.fourthline.cling.android.FixedAndroidLogHandler +import android.content.Intent +import org.fourthline.cling.UpnpServiceConfiguration +import org.fourthline.cling.android.AndroidUpnpServiceConfiguration +import org.seamless.util.logging.LoggingUtil + +/** + * + */ +class DLNACastService : AndroidUpnpServiceImpl() { + private val mLogger: ILogger = DefaultLoggerImpl(this) + override fun onCreate() { + mLogger.i(String.format("[%s] onCreate", javaClass.name)) + LoggingUtil.resetRootHandler(FixedAndroidLogHandler()) + super.onCreate() + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + mLogger.i(String.format("[%s] onStartCommand: %s , %s", javaClass.name, intent, flags)) + return super.onStartCommand(intent, flags, startId) + } + + override fun onDestroy() { + mLogger.w(String.format("[%s] onDestroy", javaClass.name)) + super.onDestroy() + } + + override fun createConfiguration(): UpnpServiceConfiguration { + return DLNACastServiceConfiguration() + } + + // ---------------------------------------------------------------- + // ---- configuration + // ---------------------------------------------------------------- + private class DLNACastServiceConfiguration : AndroidUpnpServiceConfiguration() { + override fun getRegistryMaintenanceIntervalMillis(): Int { + return 5000 //default is 3000! + } + + // @Override + // public ServiceType[] getExclusiveServiceTypes() { + // return new ServiceType[]{ + // DLNACastManager.SERVICE_RENDERING_CONTROL, + // DLNACastManager.SERVICE_AV_TRANSPORT, + // DLNACastManager.SERVICE_CONNECTION_MANAGER}; + // } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ICast.java b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ICast.java deleted file mode 100644 index 57ebb927..00000000 --- a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ICast.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.skyd.imomoe.util.dlna.dmc; - -import androidx.annotation.NonNull; - -public interface ICast { - - @NonNull - String getId(); - - @NonNull - String getUri(); - - String getName(); - - interface ICastVideo extends ICast { - - /** - * @return video duration, ms - */ - long getDurationMillSeconds(); - - long getSize(); - - long getBitrate(); - } - - interface ICastAudio extends ICast { - /** - * @return audio duration, ms - */ - long getDurationMillSeconds(); - - long getSize(); - } - - interface ICastImage extends ICast { - long getSize(); - } - -} diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ICast.kt b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ICast.kt new file mode 100644 index 00000000..c6e0b593 --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ICast.kt @@ -0,0 +1,28 @@ +package com.skyd.imomoe.util.dlna.dmc + +interface ICast { + val id: String + val uri: String + val name: String? + + interface ICastVideo : ICast { + /** + * @return video duration, ms + */ + val durationMillSeconds: Long + val size: Long + val bitrate: Long + } + + interface ICastAudio : ICast { + /** + * @return audio duration, ms + */ + val durationMillSeconds: Long + val size: Long + } + + interface ICastImage : ICast { + val size: Long + } +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ILogger.java b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ILogger.java deleted file mode 100644 index c01ec4b9..00000000 --- a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ILogger.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.skyd.imomoe.util.dlna.dmc; - -import android.text.TextUtils; -import android.util.Log; - -import com.skyd.imomoe.BuildConfig; - -public interface ILogger { - String PREFIX_TAG = "DLNACast_"; - - void v(String msg); - - void d(String msg); - - void i(String msg); - - void w(String msg); - - void e(String msg); - - class DefaultLoggerImpl implements ILogger { - private final String TAG; - private final boolean DEBUG; - - public DefaultLoggerImpl(Object object) { - this(object, BuildConfig.DEBUG); - } - - public DefaultLoggerImpl(Object object, boolean debug) { - String className = object.getClass().getSimpleName(); - if (TextUtils.isEmpty(className)) { - if (object.getClass().getSuperclass() != null) { - className = object.getClass().getSuperclass().getSimpleName(); - } else { - className = "$1"; - } - } - TAG = PREFIX_TAG + className; - DEBUG = debug; - } - - @Override - public void v(String msg) { - if (DEBUG) { - Log.v(TAG, msg); - } - } - - @Override - public void d(String msg) { - if (DEBUG) { - Log.d(TAG, msg); - } - } - - @Override - public void i(String msg) { - if (DEBUG) { - Log.i(TAG, msg); - } - } - - @Override - public void w(String msg) { - if (DEBUG) { - Log.w(TAG, msg); - } - } - - @Override - public void e(String msg) { - if (DEBUG) { - Log.e(TAG, msg); - } - } - } -} diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ILogger.kt b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ILogger.kt new file mode 100644 index 00000000..7a824c48 --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/ILogger.kt @@ -0,0 +1,56 @@ +package com.skyd.imomoe.util.dlna.dmc + +import android.text.TextUtils +import android.util.Log +import com.skyd.imomoe.BuildConfig + +interface ILogger { + fun v(msg: String?) + fun d(msg: String?) + fun i(msg: String?) + fun w(msg: String?) + fun e(msg: String?) + class DefaultLoggerImpl @JvmOverloads constructor( + `object`: Any, + debug: Boolean = BuildConfig.DEBUG + ) : ILogger { + private val TAG: String + private val DEBUG: Boolean + override fun v(msg: String?) { + if (DEBUG) Log.v(TAG, msg.toString()) + } + + override fun d(msg: String?) { + if (DEBUG) Log.d(TAG, msg.toString()) + } + + override fun i(msg: String?) { + if (DEBUG) Log.i(TAG, msg.toString()) + } + + override fun w(msg: String?) { + if (DEBUG) Log.w(TAG, msg.toString()) + } + + override fun e(msg: String?) { + if (DEBUG) Log.e(TAG, msg.toString()) + } + + init { + var className = `object`.javaClass.simpleName + if (TextUtils.isEmpty(className)) { + className = if (`object`.javaClass.superclass != null) { + `object`.javaClass.superclass.simpleName + } else { + "$1" + } + } + TAG = PREFIX_TAG + className + DEBUG = debug + } + } + + companion object { + const val PREFIX_TAG = "DLNACast_" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/OnDeviceRegistryListener.java b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/OnDeviceRegistryListener.java deleted file mode 100644 index ce358dd3..00000000 --- a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/OnDeviceRegistryListener.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.skyd.imomoe.util.dlna.dmc; - -import org.fourthline.cling.model.meta.Device; - -/** - * this listener call in UI thread. - */ -public interface OnDeviceRegistryListener { - void onDeviceAdded(Device device); - - void onDeviceUpdated(Device device); - - void onDeviceRemoved(Device device); -} diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/OnDeviceRegistryListener.kt b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/OnDeviceRegistryListener.kt new file mode 100644 index 00000000..f25ebc4f --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/util/dlna/dmc/OnDeviceRegistryListener.kt @@ -0,0 +1,12 @@ +package com.skyd.imomoe.util.dlna.dmc + +import org.fourthline.cling.model.meta.Device + +/** + * this listener call in UI thread. + */ +interface OnDeviceRegistryListener { + fun onDeviceAdded(device: Device<*, *, *>?) + fun onDeviceUpdated(device: Device<*, *, *>?) + fun onDeviceRemoved(device: Device<*, *, *>?) +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/ContentResourceServlet.java b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/ContentResourceServlet.kt similarity index 60% rename from app/src/main/java/com/skyd/imomoe/util/dlna/dms/ContentResourceServlet.java rename to app/src/main/java/com/skyd/imomoe/util/dlna/dms/ContentResourceServlet.kt index 83cf1320..13c80057 100644 --- a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/ContentResourceServlet.java +++ b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/ContentResourceServlet.kt @@ -13,34 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.skyd.imomoe.util.dlna.dms; +package com.skyd.imomoe.util.dlna.dms -import org.eclipse.jetty.servlet.DefaultServlet; -import org.eclipse.jetty.util.resource.FileResource; -import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.servlet.DefaultServlet +import org.eclipse.jetty.util.resource.FileResource +import org.eclipse.jetty.util.resource.Resource +import java.io.File +import java.lang.Exception -import java.io.File; - -public class ContentResourceServlet extends DefaultServlet { - - @Override - public Resource getResource(String pathInContext) { +open class ContentResourceServlet : DefaultServlet() { + override fun getResource(pathInContext: String?): Resource? { // String id = Utils.parseResourceId(pathInContext); // content://media/external/video/media/1611127029319529 // Uri uri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, Long.parseLong(id)); // Logger.i("ContentResourceServlet, path: %s", pathInContext); try { - File file = new File(pathInContext); - if (file.exists()) return FileResource.newResource(file); - } catch (Exception e) { - e.printStackTrace(); + val file = File(pathInContext!!) + if (file.exists()) return FileResource.newResource(file) + } catch (e: Exception) { + e.printStackTrace() } - return null; + return null } - public static class VideoResourceServlet extends ContentResourceServlet { - } - - public static class AudioResourceServlet extends ContentResourceServlet { - } + class VideoResourceServlet : ContentResourceServlet() + class AudioResourceServlet : ContentResourceServlet() } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IMediaContentDao.java b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IMediaContentDao.java deleted file mode 100644 index 6f3581c9..00000000 --- a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IMediaContentDao.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.skyd.imomoe.util.dlna.dms; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import org.fourthline.cling.support.model.item.Item; - -import java.util.List; - -interface IMediaContentDao { - @NonNull - List getImageItems(@NonNull Context context); - - @NonNull - List getAudioItems(@NonNull Context context); - - @NonNull - List getVideoItems(@NonNull Context context); -} diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IMediaContentDao.kt b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IMediaContentDao.kt new file mode 100644 index 00000000..0a0d7c35 --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IMediaContentDao.kt @@ -0,0 +1,10 @@ +package com.skyd.imomoe.util.dlna.dms + +import android.content.Context +import org.fourthline.cling.support.model.item.Item + +internal interface IMediaContentDao { + fun getImageItems(context: Context): List + fun getAudioItems(context: Context): List + fun getVideoItems(context: Context): List +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServer.java b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServer.java deleted file mode 100644 index 53ba4c95..00000000 --- a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServer.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.skyd.imomoe.util.dlna.dms; - -interface IResourceServer { - void startServer(); - - void stopServer(); -} diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServer.kt b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServer.kt new file mode 100644 index 00000000..369e30e2 --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServer.kt @@ -0,0 +1,6 @@ +package com.skyd.imomoe.util.dlna.dms + +internal interface IResourceServer { + fun startServer() + fun stopServer() +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServerFactory.java b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServerFactory.java deleted file mode 100644 index 2969322b..00000000 --- a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServerFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.skyd.imomoe.util.dlna.dms; - -interface IResourceServerFactory { - int getPort(); - - IResourceServer getInstance(); - - // ---------------------------------------------------------------------------- - // ---- implement - // ---------------------------------------------------------------------------- - final class DefaultResourceServerFactoryImpl implements IResourceServerFactory { - private final int port; - - public DefaultResourceServerFactoryImpl(int port) { - this.port = port; - } - - @Override - public int getPort() { - return port; - } - - @Override - public IResourceServer getInstance() { - // TODO: - // return new JettyHttpServer(port); - return new NanoHttpServer(port); - } - } -} diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServerFactory.kt b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServerFactory.kt new file mode 100644 index 00000000..2262bdae --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/IResourceServerFactory.kt @@ -0,0 +1,17 @@ +package com.skyd.imomoe.util.dlna.dms + + +internal interface IResourceServerFactory { + val port: Int + val instance: IResourceServer + + // ---------------------------------------------------------------------------- + // ---- implement + // ---------------------------------------------------------------------------- + class DefaultResourceServerFactoryImpl(override val port: Int) : IResourceServerFactory { + override val instance: IResourceServer + // TODO: + // return new JettyHttpServer(port); + get() = NanoHttpServer(port) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/MediaContentDao.kt b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/MediaContentDao.kt index 11a68235..ba0c0c47 100644 --- a/app/src/main/java/com/skyd/imomoe/util/dlna/dms/MediaContentDao.kt +++ b/app/src/main/java/com/skyd/imomoe/util/dlna/dms/MediaContentDao.kt @@ -1,5 +1,6 @@ package com.skyd.imomoe.util.dlna.dms +import android.annotation.SuppressLint import android.content.Context import android.provider.MediaStore.* import org.fourthline.cling.support.model.PersonWithRole @@ -12,6 +13,7 @@ import java.io.File import java.util.* internal class MediaContentDao(private val mBaseUrl: String) : IMediaContentDao { + @SuppressLint("Range") override fun getImageItems(context: Context): List { val items: MutableList = ArrayList() context.contentResolver.query( @@ -39,6 +41,7 @@ internal class MediaContentDao(private val mBaseUrl: String) : IMediaContentDao return items } + @SuppressLint("Range") override fun getAudioItems(context: Context): List { val items: MutableList = ArrayList() context.contentResolver.query( @@ -75,6 +78,7 @@ internal class MediaContentDao(private val mBaseUrl: String) : IMediaContentDao return items } + @SuppressLint("Range") override fun getVideoItems(context: Context): List { val items: MutableList = ArrayList() context.contentResolver.query( diff --git a/app/src/main/java/com/skyd/imomoe/util/downloadanime/AnimeDownloadHelper.kt b/app/src/main/java/com/skyd/imomoe/util/downloadanime/AnimeDownloadHelper.kt index abf759be..ac144a6c 100644 --- a/app/src/main/java/com/skyd/imomoe/util/downloadanime/AnimeDownloadHelper.kt +++ b/app/src/main/java/com/skyd/imomoe/util/downloadanime/AnimeDownloadHelper.kt @@ -4,14 +4,12 @@ import android.content.Intent import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.hjq.permissions.OnPermissionCallback -import com.hjq.permissions.Permission -import com.hjq.permissions.XXPermissions import com.skyd.imomoe.App import com.skyd.imomoe.R import com.skyd.imomoe.config.Const import com.skyd.imomoe.config.Const.DownloadAnime.Companion.animeFilePath import com.skyd.imomoe.database.entity.AnimeDownloadEntity +import com.skyd.imomoe.util.requestManageExternalStorage import com.skyd.imomoe.util.showToast import org.w3c.dom.Element import org.w3c.dom.Node @@ -244,32 +242,23 @@ class AnimeDownloadHelper private constructor() { .showToast() return } - XXPermissions.with(activity).permission(Permission.MANAGE_EXTERNAL_STORAGE).request( - object : OnPermissionCallback { - override fun onGranted(permissions: MutableList?, all: Boolean) { - if (downloadHashMap[key]?.value == AnimeDownloadStatus.DOWNLOADING) { - "已经在下载啦...".showToast() - return - } /*else if (downloadHashMap[key]?.value == AnimeDownloadStatus.COMPLETE) { - "已经下载好啦...".showToast() - return - }*/ - val status = MutableLiveData() - status.value = AnimeDownloadStatus.DOWNLOADING - downloadHashMap[key] = status - activity.startService( - Intent(activity, AnimeDownloadService::class.java) - .putExtra("url", url) - .putExtra("key", key) - .putExtra("folderAndFileName", folderAndFileName) - ) - } - - override fun onDenied(permissions: MutableList?, never: Boolean) { - super.onDenied(permissions, never) - "未获取存储权限,无法下载".showToast() + activity.requestManageExternalStorage { + onGranted { + if (downloadHashMap[key]?.value == AnimeDownloadStatus.DOWNLOADING) { + "已经在下载啦...".showToast() + return@onGranted } + val status = MutableLiveData() + status.value = AnimeDownloadStatus.DOWNLOADING + downloadHashMap[key] = status + activity.startService( + Intent(activity, AnimeDownloadService::class.java) + .putExtra("url", url) + .putExtra("key", key) + .putExtra("folderAndFileName", folderAndFileName) + ) } - ) + onDenied { "未获取存储权限,无法下载".showToast() } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/view/activity/AnimeDetailActivity.kt b/app/src/main/java/com/skyd/imomoe/view/activity/AnimeDetailActivity.kt index b66ae7d2..1a5974d0 100644 --- a/app/src/main/java/com/skyd/imomoe/view/activity/AnimeDetailActivity.kt +++ b/app/src/main/java/com/skyd/imomoe/view/activity/AnimeDetailActivity.kt @@ -13,7 +13,6 @@ import com.skyd.imomoe.config.Const import com.skyd.imomoe.database.getAppDataBase import com.skyd.imomoe.databinding.ActivityAnimeDetailBinding import com.skyd.imomoe.util.Util.getSkinResourceId -import com.skyd.imomoe.util.Util.getStatusBarHeight import com.skyd.imomoe.util.Util.setTransparentStatusBar import com.skyd.imomoe.util.showToast import com.skyd.imomoe.util.coil.DarkBlurTransformation @@ -41,12 +40,7 @@ class AnimeDetailActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setTransparentStatusBar(window, isDark = true) - - val statusBarLinearParams = - mBinding.viewAnimeDetailActivityStatusBar.layoutParams //取控件当前的布局参数 - statusBarLinearParams.height = getStatusBarHeight() - mBinding.viewAnimeDetailActivityStatusBar.layoutParams = statusBarLinearParams + setTransparentStatusBar(window, isDark = false) viewModel = ViewModelProvider(this).get(AnimeDetailViewModel::class.java) adapter = AnimeDetailAdapter(this, viewModel.animeDetailList) diff --git a/app/src/main/java/com/skyd/imomoe/view/activity/AnimeDownloadActivity.kt b/app/src/main/java/com/skyd/imomoe/view/activity/AnimeDownloadActivity.kt index 6720633f..0b5e0456 100644 --- a/app/src/main/java/com/skyd/imomoe/view/activity/AnimeDownloadActivity.kt +++ b/app/src/main/java/com/skyd/imomoe/view/activity/AnimeDownloadActivity.kt @@ -7,13 +7,11 @@ import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.MaterialDialog -import com.hjq.permissions.OnPermissionCallback -import com.hjq.permissions.Permission -import com.hjq.permissions.XXPermissions import com.skyd.imomoe.R import com.skyd.imomoe.databinding.ActivityAnimeDownloadBinding import com.skyd.imomoe.util.showToast import com.skyd.imomoe.util.gone +import com.skyd.imomoe.util.requestManageExternalStorage import com.skyd.imomoe.util.visible import com.skyd.imomoe.view.adapter.AnimeDownloadAdapter import com.skyd.imomoe.viewmodel.AnimeDownloadViewModel @@ -70,23 +68,19 @@ class AnimeDownloadActivity : BaseActivity() { } }) - XXPermissions.with(this).permission(Permission.MANAGE_EXTERNAL_STORAGE) - .request(object : OnPermissionCallback { - override fun onGranted(permissions: MutableList?, all: Boolean) { - if (mode == 0) viewModel.getAnimeCover() - else if (mode == 1) { - mBinding.layoutAnimeDownloadLoading.layoutCircleProgressTextTip1.visible() - viewModel.getAnimeCoverEpisode(directoryName, path) - } - } - - override fun onDenied(permissions: MutableList?, never: Boolean) { - super.onDenied(permissions, never) - "无存储权限,无法播放本地缓存视频".showToast(Toast.LENGTH_LONG) - finish() + requestManageExternalStorage { + onGranted { + if (mode == 0) viewModel.getAnimeCover() + else if (mode == 1) { + mBinding.layoutAnimeDownloadLoading.layoutCircleProgressTextTip1.visible() + viewModel.getAnimeCoverEpisode(directoryName, path) } } - ) + onDenied { + "无存储权限,无法播放本地缓存视频".showToast(Toast.LENGTH_LONG) + finish() + } + } } override fun getBinding(): ActivityAnimeDownloadBinding = diff --git a/app/src/main/java/com/skyd/imomoe/view/activity/ConfigDataSourceActivity.kt b/app/src/main/java/com/skyd/imomoe/view/activity/ConfigDataSourceActivity.kt index 564f42f7..d63f96c9 100644 --- a/app/src/main/java/com/skyd/imomoe/view/activity/ConfigDataSourceActivity.kt +++ b/app/src/main/java/com/skyd/imomoe/view/activity/ConfigDataSourceActivity.kt @@ -49,17 +49,29 @@ class ConfigDataSourceActivity : BaseActivity() } private fun callToImport(intent: Intent) { - if (Intent.ACTION_VIEW == intent.action) { - intent.data?.let { uri -> - importDataSource(uri, - onSuccess = { - getString(R.string.import_data_source_success, uri.path).showSnackbar(this) - viewModel.getDataSourceList() - }, - onFailed = { - getString(R.string.import_data_source_failed, it.message).showSnackbar(this) - } - ) + val uri = intent.data + if (Intent.ACTION_VIEW == intent.action && uri != null) { + requestManageExternalStorage { + onGranted { + importDataSource(uri, + onSuccess = { + getString( + R.string.import_data_source_success, + uri.path + ).showSnackbar(this@ConfigDataSourceActivity) + viewModel.getDataSourceList() + }, + onFailed = { + getString( + R.string.import_data_source_failed, + it.message + ).showSnackbar(this@ConfigDataSourceActivity) + } + ) + } + onDenied { + "无存储权限,无法导入".showSnackbar(this@ConfigDataSourceActivity, Toast.LENGTH_LONG) + } } } } diff --git a/app/src/main/java/com/skyd/imomoe/view/activity/PlayActivity.kt b/app/src/main/java/com/skyd/imomoe/view/activity/PlayActivity.kt index ea8f6e50..44352ab8 100644 --- a/app/src/main/java/com/skyd/imomoe/view/activity/PlayActivity.kt +++ b/app/src/main/java/com/skyd/imomoe/view/activity/PlayActivity.kt @@ -92,9 +92,11 @@ class PlayActivity : DetailPlayerActivity when { abs(verticalOffset) > ctlPlayActivity.scrimVisibleHeightTrigger -> { - tvPlayActivityToolbarTitle?.visible() + tvPlayActivityToolbarVideoTitle.gone() + tvPlayActivityToolbarTitle?.visible(animate = true, dur = 200L) } else -> { + tvPlayActivityToolbarVideoTitle.visible(animate = true, dur = 200L) tvPlayActivityToolbarTitle?.gone() } } @@ -285,19 +287,16 @@ class PlayActivity : DetailPlayerActivity danmakuParamMap.clear() danmakuParamMap.putAll(paramMap) danmakuUrl = paramMap[SnifferVideo.DANMU_URL] ?: "" - setUp(videoUrl, false, title) + setUp(videoUrl, false, episodeDataBean.title) // 开始播放 startPlayLogic() } @@ -409,13 +408,6 @@ class PlayActivity : DetailPlayerActivity -// videoPlayer.startPlay(videoUrl, viewModel.animeEpisodeDataBean.title) -// } } override fun onQuitFullscreen(url: String?, vararg objects: Any?) { @@ -448,8 +440,12 @@ class PlayActivity : DetailPlayerActivity = ArrayList() private val buttonLayoutParams = LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.MATCH_PARENT + ViewGroup.LayoutParams.WRAP_CONTENT ) private lateinit var titleTextView: TextView private lateinit var backButton: ImageView @@ -85,6 +88,7 @@ class AnimeToolbar : LinearLayout { backButton = ImageView(context, attrs).apply { this.layoutParams = buttonLayoutParams adjustViewBounds = true + minimumHeight = BUTTON_MIN_HEIGHT.dp context.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackground)) .also { background = it.getDrawable(0) @@ -152,6 +156,7 @@ class AnimeToolbar : LinearLayout { addView(ImageView(context).apply { layoutParams = params adjustViewBounds = true + minimumHeight = BUTTON_MIN_HEIGHT.dp context.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackground)) .also { background = it.getDrawable(0) }.recycle() setPadding(12.dp, 12.dp, 12.dp, 12.dp) diff --git a/app/src/main/java/com/skyd/imomoe/view/component/player/AnimeVideoPlayer.kt b/app/src/main/java/com/skyd/imomoe/view/component/player/AnimeVideoPlayer.kt index 7dc63cf8..1d6f95f2 100644 --- a/app/src/main/java/com/skyd/imomoe/view/component/player/AnimeVideoPlayer.kt +++ b/app/src/main/java/com/skyd/imomoe/view/component/player/AnimeVideoPlayer.kt @@ -374,8 +374,8 @@ open class AnimeVideoPlayer : StandardGSYVideoPlayer { override fun hideAllWidget() { super.hideAllWidget() - setViewShowState(vgRightContainer, INVISIBLE) - setViewShowState(vgSettingContainer, INVISIBLE) +// setViewShowState(vgRightContainer, INVISIBLE) +// setViewShowState(vgSettingContainer, INVISIBLE) setViewShowState(tvRestoreScreen, View.GONE) setViewShowState(viewTopContainerShadow, View.INVISIBLE) } @@ -726,25 +726,58 @@ open class AnimeVideoPlayer : StandardGSYVideoPlayer { override fun onClick(v: View) { super.onClick(v) - val i = v.id - // bigger_surface代替原有的surface_container执行点击动作 - if (i == R.id.bigger_surface && mCurrentState == GSYVideoView.CURRENT_STATE_ERROR) { - if (mVideoAllCallBack != null) { - Debuger.printfLog("onClickStartError") - mVideoAllCallBack.onClickStartError(mOriginUrl, mTitle, this) - } - prepareVideo() - } else if (i == R.id.bigger_surface) { - if (mVideoAllCallBack != null && isCurrentMediaListener) { - if (mIfCurrentIsFullscreen) { - Debuger.printfLog("onClickBlankFullscreen") - mVideoAllCallBack.onClickBlankFullscreen(mOriginUrl, mTitle, this) + when (v.id) { + // bigger_surface代替原有的surface_container执行点击动作 + R.id.bigger_surface -> { + vgSettingContainer?.gone() + vgRightContainer?.gone() + if (mCurrentState == GSYVideoView.CURRENT_STATE_ERROR) { + if (mVideoAllCallBack != null) { + Debuger.printfLog("onClickStartError") + mVideoAllCallBack.onClickStartError(mOriginUrl, mTitle, this) + } + prepareVideo() } else { - Debuger.printfLog("onClickBlank") - mVideoAllCallBack.onClickBlank(mOriginUrl, mTitle, this) + if (mVideoAllCallBack != null && isCurrentMediaListener) { + if (mIfCurrentIsFullscreen) { + Debuger.printfLog("onClickBlankFullscreen") + mVideoAllCallBack.onClickBlankFullscreen(mOriginUrl, mTitle, this) + } else { + Debuger.printfLog("onClickBlank") + mVideoAllCallBack.onClickBlank(mOriginUrl, mTitle, this) + } + } + startDismissControlViewTimer() } } - startDismissControlViewTimer() + R.id.thumb -> { + vgSettingContainer?.gone() + vgRightContainer?.gone() + } + } + } + + /** + * 双击的时候调用此方法 + */ + override fun touchDoubleUp(e: MotionEvent?) { + // 处理双击前的逻辑 + val oldUiVisibilityState = mBottomContainer?.visibility ?: VISIBLE + + // 处理双击 + super.touchDoubleUp(e) + + // 下面是处理完双击后的逻辑 + if (mCurrentState == CURRENT_STATE_PLAYING) { // 若双击后是播放状态 + //双击前Ui是什么可见性状态,则双击后Ui还是什么可见性状态,避免双击后Ui突然显示出来 + if (oldUiVisibilityState == VISIBLE) changeUiToPlayingShow() + else changeUiToPlayingClear() +// cancelDismissControlViewTimer() + } else if (mCurrentState == CURRENT_STATE_PAUSE) { // 若双击后是暂停状态 + //双击前Ui是什么可见性状态,则双击后Ui还是什么可见性状态,避免双击后Ui突然显示出来 + if (oldUiVisibilityState == VISIBLE) changeUiToPauseShow() + else changeUiToPauseClear() +// cancelDismissControlViewTimer() } } diff --git a/app/src/main/java/com/skyd/imomoe/view/component/player/DanmakuVideoPlayer.kt b/app/src/main/java/com/skyd/imomoe/view/component/player/DanmakuVideoPlayer.kt index e4d872e2..4cd291f2 100644 --- a/app/src/main/java/com/skyd/imomoe/view/component/player/DanmakuVideoPlayer.kt +++ b/app/src/main/java/com/skyd/imomoe/view/component/player/DanmakuVideoPlayer.kt @@ -9,6 +9,7 @@ import android.view.ViewGroup import android.view.inputmethod.EditorInfo import android.widget.EditText import android.widget.ImageView +import android.widget.SeekBar import android.widget.TextView import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.input.input @@ -35,6 +36,7 @@ import com.skyd.imomoe.view.component.player.danmaku.Const import com.skyd.imomoe.view.component.player.danmaku.anime.AnimeDanmakuParser import com.skyd.imomoe.view.component.player.danmaku.anime.AnimeDanmakuSender import com.skyd.imomoe.view.component.player.danmaku.bili.BiliBiliDanmakuParser +import com.skyd.imomoe.view.listener.dsl.setOnSeekBarChangeListener import java.net.HttpURLConnection import java.net.URL import java.util.zip.Inflater @@ -93,6 +95,21 @@ open class DanmakuVideoPlayer : AnimeVideoPlayer { // 弹幕进度delta private var mDanmakuProgressDelta: Long = 0L + // 弹幕字号缩放百分比SeekBar + private var sbDanmakuTextScale: SeekBar? = null + + // "弹幕字号"TextView + private var tvDanmakuTextScaleHeader: TextView? = null + + // 显示弹幕字号缩放百分比TextView + private var tvDanmakuTextScale: TextView? = null + + // 弹幕字号缩放最小百分比 + private val mDanmakuTextScaleMinPercent: Int = 50 + + // 弹幕字号百分比 + private var mDanmakuTextScalePercent: Int = mDanmakuTextScaleMinPercent + 70 + constructor(context: Context, fullFlag: Boolean?) : super(context, fullFlag) constructor(context: Context) : super(context) @@ -112,6 +129,9 @@ open class DanmakuVideoPlayer : AnimeVideoPlayer { tvRewindDanmakuProgress = findViewById(R.id.tv_player_rewind_danmaku_progress) tvResetDanmakuProgress = findViewById(R.id.tv_player_reset_danmaku_progress) tvForwardDanmakuProgress = findViewById(R.id.tv_player_forward_danmaku_progress) + sbDanmakuTextScale = findViewById(R.id.sb_danmaku_text_size_scale) + tvDanmakuTextScaleHeader = findViewById(R.id.tv_danmaku_text_size_scale_header) + tvDanmakuTextScale = findViewById(R.id.tv_danmaku_text_size_scale) etDanmakuInput?.gone() ivShowDanmaku?.gone() // 设置高度是0 @@ -204,6 +224,15 @@ open class DanmakuVideoPlayer : AnimeVideoPlayer { mDanmakuProgressDelta = 0L seekDanmaku(currentPlayer.currentPositionWhenPlaying.toLong()) } + + sbDanmakuTextScale?.setOnSeekBarChangeListener { + onProgressChanged { seekBar, progress, _ -> + seekBar ?: return@onProgressChanged + mDanmakuTextScalePercent = progress + mDanmakuTextScaleMinPercent + setTextSizeScale(mDanmakuTextScalePercent / 100f) + tvDanmakuTextScale?.text = mDanmakuTextScalePercent.percentage + } + } } override fun onCompletion() { @@ -296,6 +325,8 @@ open class DanmakuVideoPlayer : AnimeVideoPlayer { super.startWindowFullscreen(context, actionBar, statusBar) as DanmakuVideoPlayer player.ivShowDanmaku?.visibility = ivShowDanmaku?.visibility ?: View.GONE player.etDanmakuInput?.visibility = etDanmakuInput?.visibility ?: View.GONE + player.sbDanmakuTextScale?.progress = mDanmakuTextScalePercent - mDanmakuTextScaleMinPercent + player.setTextSizeScale(mDanmakuTextScalePercent / 100f) player.mDanmakuShow = mDanmakuShow player.resolveDanmakuShow() @@ -325,10 +356,11 @@ open class DanmakuVideoPlayer : AnimeVideoPlayer { val player = it as DanmakuVideoPlayer if (player.etDanmakuInput?.visibility == View.VISIBLE) showBottomDanmakuController() else hideBottomDanmakuController() - ivShowDanmaku?.visibility = - player.ivShowDanmaku?.visibility ?: View.GONE - etDanmakuInput?.visibility = - player.etDanmakuInput?.visibility ?: View.GONE + ivShowDanmaku?.visibility = player.ivShowDanmaku?.visibility ?: View.GONE + etDanmakuInput?.visibility = player.etDanmakuInput?.visibility ?: View.GONE + mDanmakuTextScalePercent = + (player.sbDanmakuTextScale?.progress ?: 0) + mDanmakuTextScaleMinPercent + setTextSizeScale(player.mDanmakuTextScalePercent / 100f) mDanmakuShow = player.mDanmakuShow resolveDanmakuShow() @@ -434,6 +466,9 @@ open class DanmakuVideoPlayer : AnimeVideoPlayer { tvRewindDanmakuProgress?.visible() tvResetDanmakuProgress?.visible() tvForwardDanmakuProgress?.visible() + sbDanmakuTextScale?.visible() + tvDanmakuTextScaleHeader?.visible() + tvDanmakuTextScale?.visible() if (mDanmakuParamMap.size > 0) { etDanmakuInput?.enable() etDanmakuInput?.hint = mContext.getString(R.string.send_a_danmaku) @@ -493,6 +528,15 @@ open class DanmakuVideoPlayer : AnimeVideoPlayer { stopDanmaku() } + /** + * 更改弹幕字号缩放百分比 + * @param scale 缩放倍数,例如2.7f指的是弹幕字号乘2.7 + */ + private fun setTextSizeScale(scale: Float) { + config = config.copy(textSizeScale = scale) + mDanmakuPlayer.updateConfig(config) + } + /** * 释放弹幕控件 */ diff --git a/app/src/main/java/com/skyd/imomoe/view/component/player/DetailPlayerActivity.java b/app/src/main/java/com/skyd/imomoe/view/component/player/DetailPlayerActivity.java deleted file mode 100644 index f0c2b635..00000000 --- a/app/src/main/java/com/skyd/imomoe/view/component/player/DetailPlayerActivity.java +++ /dev/null @@ -1,338 +0,0 @@ -package com.skyd.imomoe.view.component.player; - -import android.content.res.Configuration; -import android.os.Bundle; -import android.view.View; - -import androidx.viewbinding.ViewBinding; - -import com.skyd.imomoe.view.activity.BaseActivity; -import com.skyd.skin.core.SkinBaseActivity; -import com.shuyu.gsyvideoplayer.GSYVideoManager; -import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder; -import com.shuyu.gsyvideoplayer.utils.OrientationOption; -import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer; - -import org.jetbrains.annotations.NotNull; - -import static com.shuyu.gsyvideoplayer.video.base.GSYVideoView.CURRENT_STATE_PAUSE; - -/** - * 详情模式播放页面基础类 - */ -public abstract class DetailPlayerActivity extends BaseActivity implements MyVideoAllCallBack { - - protected boolean isPlay; - - // 是否是在onPause方法里自动暂停的 - protected boolean isPause; - - protected AnimeOrientationUtils orientationUtils; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - /** - * 选择普通模式 - */ - public void initVideo() { - //外部辅助的旋转,帮助全屏 - orientationUtils = new AnimeOrientationUtils(this, getGSYVideoPlayer(), getOrientationOption()); - //初始化不打开外部的旋转 - orientationUtils.setEnable(false); - if (getGSYVideoPlayer().getFullscreenButton() != null) { - getGSYVideoPlayer().getFullscreenButton().setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showFull(); - clickForFullScreen(); - } - }); - } - // 退出全屏监听,避免平板退出全屏后变成竖屏 - getGSYVideoPlayer().setBackFromFullScreenListener(view -> { - onBackPressed(); - }); - } - - /** - * 选择builder模式 - */ - public void initVideoBuilderMode() { - initVideo(); - getGSYVideoOptionBuilder(). - setVideoAllCallBack(this) - .build(getGSYVideoPlayer()); - } - - public void showFull() { - if (orientationUtils.getIsLand() != 1) { - //直接横屏 - orientationUtils.resolveByClick(); - } - //第一个true是否需要隐藏actionbar,第二个true是否需要隐藏statusBar - getGSYVideoPlayer().startWindowFullscreen(DetailPlayerActivity.this, hideActionBarWhenFull(), hideStatusBarWhenFull()); - - } - - @Override - public void onBackPressed() { - if (orientationUtils != null) { - orientationUtils.backToProtVideo2(); - } - if (GSYVideoManager.backFromWindowFull(this)) { - return; - } - super.onBackPressed(); - } - - - @Override - protected void onPause() { - super.onPause(); - if (getGSYVideoPlayer().getCurrentPlayer().getCurrentState() != CURRENT_STATE_PAUSE) { - getGSYVideoPlayer().getCurrentPlayer().onVideoPause(); - if (orientationUtils != null) { - orientationUtils.setIsPause(true); - } - isPause = true; - } - } - - @Override - protected void onResume() { - super.onResume(); - if (isPause) { - getGSYVideoPlayer().getCurrentPlayer().onVideoResume(); - if (orientationUtils != null) { - orientationUtils.setIsPause(false); - } - isPause = false; - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (isPlay) { - getGSYVideoPlayer().getCurrentPlayer().release(); - } - if (orientationUtils != null) - orientationUtils.releaseListener(); - } - - /** - * orientationUtils 和 detailPlayer.onConfigurationChanged 方法是用于触发屏幕旋转的 - */ - @Override - public void onConfigurationChanged(@NotNull Configuration newConfig) { - super.onConfigurationChanged(newConfig); - //如果旋转了就全屏 - if (isPlay && !isPause) { - getGSYVideoPlayer().onConfigurationChanged(this, newConfig, orientationUtils, hideActionBarWhenFull(), hideStatusBarWhenFull()); - } - } - - @Override - public void onStartPrepared(String url, Object... objects) { - videoPlayStatusChanged(true); - needShowToolbar(true); - } - - @Override - public void onPrepared(String url, Object... objects) { - - if (orientationUtils == null) { - throw new NullPointerException("initVideo() or initVideoBuilderMode() first"); - } - //开始播放了才能旋转和全屏 - orientationUtils.setEnable(getDetailOrientationRotateAuto() && !isAutoFullWithSize()); - isPlay = true; - isPause = false; - videoPlayStatusChanged(true); - } - - @Override - public void onClickStartIcon(String url, Object... objects) { - - } - - @Override - public void onClickStartError(String url, Object... objects) { - - } - - @Override - public void onClickStop(String url, Object... objects) { - videoPlayStatusChanged(false); - } - - @Override - public void onClickStopFullscreen(String url, Object... objects) { - videoPlayStatusChanged(false); - } - - @Override - public void onClickResume(String url, Object... objects) { - videoPlayStatusChanged(true); - } - - @Override - public void onClickResumeFullscreen(String url, Object... objects) { - videoPlayStatusChanged(true); - } - - @Override - public void onClickSeekbar(String url, Object... objects) { - - } - - @Override - public void onClickSeekbarFullscreen(String url, Object... objects) { - - } - - @Override - public void onAutoComplete(String url, Object... objects) { - videoPlayStatusChanged(false); - needShowToolbar(true); - } - - @Override - public void onEnterFullscreen(String url, Object... objects) { - - } - - @Override - public void onQuitFullscreen(String url, Object... objects) { - if (orientationUtils != null) { - orientationUtils.backToProtVideo(); - } - } - - @Override - public void onQuitSmallWidget(String url, Object... objects) { - - } - - @Override - public void onEnterSmallWidget(String url, Object... objects) { - - } - - @Override - public void onTouchScreenSeekVolume(String url, Object... objects) { - - } - - @Override - public void onTouchScreenSeekPosition(String url, Object... objects) { - - } - - @Override - public void onTouchScreenSeekLight(String url, Object... objects) { - - } - - @Override - public void onPlayError(String url, Object... objects) { - videoPlayStatusChanged(false); - needShowToolbar(true); - } - - @Override - public void onClickStartThumb(String url, Object... objects) { - - } - - @Override - public void onClickBlank(String url, Object... objects) { - - } - - @Override - public void onClickBlankFullscreen(String url, Object... objects) { - - } - - @Override - public void onComplete(String url, Object... objects) { - videoPlayStatusChanged(false); - needShowToolbar(true); - } - - public boolean hideActionBarWhenFull() { - return true; - } - - public boolean hideStatusBarWhenFull() { - return true; - } - - /** - * 可配置旋转 OrientationUtils - */ - public OrientationOption getOrientationOption() { - return null; - } - - /** - * 播放控件 - */ - public abstract T getGSYVideoPlayer(); - - /** - * 配置播放器 - */ - public abstract GSYVideoOptionBuilder getGSYVideoOptionBuilder(); - - /** - * 点击了全屏 - */ - public abstract void clickForFullScreen(); - - /** - * 是否启动旋转横屏,true表示启动 - */ - public abstract boolean getDetailOrientationRotateAuto(); - - /** - * 是否根据视频尺寸,自动选择竖屏全屏或者横屏全屏,注意,这时候默认旋转无效 - */ - public boolean isAutoFullWithSize() { - return false; - } - - @Override - public void onVideoPause() { - videoPlayStatusChanged(false); - needShowToolbar(true); - } - - @Override - public void onVideoResume() { - videoPlayStatusChanged(true); - needShowToolbar(false); - } - - /** - * 视频播放状态变化 - * - * @param playing false:未在播放(包括播放失败暂停等等);true:正在播放(包括正在准备加载、缓冲等等) - */ - protected void videoPlayStatusChanged(boolean playing) { - - } - - /** - * 是否需要必须显示工具栏 - * - * @param show false:不需要显示;true:需要显示 - */ - protected void needShowToolbar(boolean show) { - - } -} diff --git a/app/src/main/java/com/skyd/imomoe/view/component/player/DetailPlayerActivity.kt b/app/src/main/java/com/skyd/imomoe/view/component/player/DetailPlayerActivity.kt new file mode 100644 index 00000000..1630d2b2 --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/view/component/player/DetailPlayerActivity.kt @@ -0,0 +1,224 @@ +package com.skyd.imomoe.view.component.player + +import android.content.res.Configuration +import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer +import androidx.viewbinding.ViewBinding +import com.skyd.imomoe.view.activity.BaseActivity +import com.shuyu.gsyvideoplayer.GSYVideoManager +import com.shuyu.gsyvideoplayer.video.base.GSYVideoView +import com.shuyu.gsyvideoplayer.utils.OrientationOption +import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder +import java.lang.NullPointerException + +/** + * 详情模式播放页面基础类 + */ +abstract class DetailPlayerActivity : BaseActivity(), + MyVideoAllCallBack { + protected open var isPlay = false + + // 是否是在onPause方法里自动暂停的 + protected open var isPause = false + protected open var orientationUtils: AnimeOrientationUtils? = null + + /** + * 选择普通模式 + */ + protected open fun initVideo() { + //外部辅助的旋转,帮助全屏 + orientationUtils = AnimeOrientationUtils(this, getGSYVideoPlayer(), orientationOption).apply { + // 初始化不打开外部的旋转 + isEnable = false + } + if (getGSYVideoPlayer().fullscreenButton != null) { + getGSYVideoPlayer().fullscreenButton.setOnClickListener { + showFull() + clickForFullScreen() + } + } + // 退出全屏监听,避免平板退出全屏后变成竖屏 + getGSYVideoPlayer().setBackFromFullScreenListener { onBackPressed() } + } + + /** + * 选择builder模式 + */ + fun initVideoBuilderMode() { + initVideo() + gsyVideoOptionBuilder.setVideoAllCallBack(this).build(getGSYVideoPlayer()) + } + + protected open fun showFull() { + if (orientationUtils?.isLand != 1) { + //直接横屏 + orientationUtils?.resolveByClick() + } + //第一个true是否需要隐藏actionbar,第二个true是否需要隐藏statusBar + getGSYVideoPlayer().startWindowFullscreen( + this@DetailPlayerActivity, + hideActionBarWhenFull(), + hideStatusBarWhenFull() + ) + } + + override fun onBackPressed() { + orientationUtils?.backToProtVideo2() + if (GSYVideoManager.backFromWindowFull(this)) { + return + } + super.onBackPressed() + } + + override fun onPause() { + super.onPause() + if (getGSYVideoPlayer().currentPlayer.currentState != GSYVideoView.CURRENT_STATE_PAUSE) { + getGSYVideoPlayer().currentPlayer.onVideoPause() + orientationUtils?.setIsPause(true) + isPause = true + } + } + + override fun onResume() { + super.onResume() + if (isPause) { + getGSYVideoPlayer().currentPlayer.onVideoResume() + orientationUtils?.setIsPause(false) + isPause = false + } + } + + override fun onDestroy() { + super.onDestroy() + if (isPlay) { + getGSYVideoPlayer().currentPlayer.release() + } + orientationUtils?.releaseListener() + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + //如果旋转了就全屏 + if (isPlay && !isPause) { + getGSYVideoPlayer().onConfigurationChanged( + this, + newConfig, + orientationUtils, + hideActionBarWhenFull(), + hideStatusBarWhenFull() + ) + } + } + + override fun onStartPrepared(url: String?, vararg objects: Any?) { + videoPlayStatusChanged(true) + } + + override fun onPrepared(url: String?, vararg objects: Any?) { + orientationUtils.let { + if (it == null) { + throw NullPointerException("initVideo() or initVideoBuilderMode() first") + } + // 开始播放了才能旋转和全屏 + it.isEnable = detailOrientationRotateAuto && !isAutoFullWithSize + isPlay = true + isPause = false + videoPlayStatusChanged(true) + } + } + + override fun onClickStartIcon(url: String?, vararg objects: Any?) {} + override fun onClickStartError(url: String?, vararg objects: Any?) {} + override fun onClickStop(url: String?, vararg objects: Any?) { + videoPlayStatusChanged(false) + } + + override fun onClickStopFullscreen(url: String?, vararg objects: Any?) { + videoPlayStatusChanged(false) + } + + override fun onClickResume(url: String?, vararg objects: Any?) { + videoPlayStatusChanged(true) + } + + override fun onClickResumeFullscreen(url: String?, vararg objects: Any?) { + videoPlayStatusChanged(true) + } + + override fun onClickSeekbar(url: String?, vararg objects: Any?) {} + override fun onClickSeekbarFullscreen(url: String?, vararg objects: Any?) {} + override fun onAutoComplete(url: String?, vararg objects: Any?) { + videoPlayStatusChanged(false) + } + + override fun onEnterFullscreen(url: String?, vararg objects: Any?) {} + override fun onQuitFullscreen(url: String?, vararg objects: Any?) { + orientationUtils?.backToProtVideo() + } + + override fun onQuitSmallWidget(url: String?, vararg objects: Any?) {} + override fun onEnterSmallWidget(url: String?, vararg objects: Any?) {} + override fun onTouchScreenSeekVolume(url: String?, vararg objects: Any?) {} + override fun onTouchScreenSeekPosition(url: String?, vararg objects: Any?) {} + override fun onTouchScreenSeekLight(url: String?, vararg objects: Any?) {} + override fun onPlayError(url: String?, vararg objects: Any?) { + videoPlayStatusChanged(false) + } + + override fun onClickStartThumb(url: String?, vararg objects: Any?) {} + override fun onClickBlank(url: String?, vararg objects: Any?) {} + override fun onClickBlankFullscreen(url: String?, vararg objects: Any?) {} + override fun onComplete(url: String?, vararg objects: Any?) { + videoPlayStatusChanged(false) + } + + protected open fun hideActionBarWhenFull(): Boolean = true + + protected open fun hideStatusBarWhenFull(): Boolean = true + + /** + * 可配置旋转 OrientationUtils + */ + protected open val orientationOption: OrientationOption? + get() = null + + /** + * 播放控件 + */ + abstract fun getGSYVideoPlayer(): T + + /** + * 配置播放器 + */ + abstract val gsyVideoOptionBuilder: GSYVideoOptionBuilder + + /** + * 点击了全屏 + */ + abstract fun clickForFullScreen() + + /** + * 是否启动旋转横屏,true表示启动 + */ + abstract val detailOrientationRotateAuto: Boolean + + /** + * 是否根据视频尺寸,自动选择竖屏全屏或者横屏全屏,注意,这时候默认旋转无效 + */ + protected open val isAutoFullWithSize: Boolean + get() = false + + override fun onVideoPause() { + videoPlayStatusChanged(false) + } + + override fun onVideoResume() { + videoPlayStatusChanged(true) + } + + /** + * 视频播放状态变化 + * + * @param playing false:未在播放(包括播放失败暂停等等);true:正在播放(包括正在准备加载、缓冲等等) + */ + protected open fun videoPlayStatusChanged(playing: Boolean) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/imomoe/view/component/player/danmaku/bili/BiliBiliDanmakuParser.kt b/app/src/main/java/com/skyd/imomoe/view/component/player/danmaku/bili/BiliBiliDanmakuParser.kt index f14990d9..3191eb91 100644 --- a/app/src/main/java/com/skyd/imomoe/view/component/player/danmaku/bili/BiliBiliDanmakuParser.kt +++ b/app/src/main/java/com/skyd/imomoe/view/component/player/danmaku/bili/BiliBiliDanmakuParser.kt @@ -10,6 +10,7 @@ import org.xml.sax.helpers.DefaultHandler import org.xml.sax.helpers.XMLReaderFactory import java.io.IOException import java.io.InputStream +import java.lang.NumberFormatException import java.util.* import kotlin.collections.ArrayList @@ -80,14 +81,18 @@ class BiliBiliDanmakuParser { val values = pValue.split(",").toTypedArray() if (values.isNotEmpty()) { - item = DanmakuItemData( - content = "", - danmakuId = values[7].toLong(), - textSize = (values[2].toFloat() * 2f).dp.toInt(), - textColor = values[3].toInt(), - position = (values[0].toFloat() * 1000).toLong(), - mode = getType(values[1].toInt()) - ) + try { + item = DanmakuItemData( + content = "", + danmakuId = values[7].toLong(), + textSize = getTextSize(values[2].toInt()).toInt(), + textColor = values[3].toInt(), + position = (values[0].toFloat() * 1000).toLong(), + mode = getType(values[1].toInt()) + ) + } catch (e: NumberFormatException) { + e.printStackTrace() + } } } } @@ -133,6 +138,25 @@ class BiliBiliDanmakuParser { } companion object { + /** + * 获取真实的字体大小px + * 弹幕库限制字体最小12f最大25f + * @param n 12非常小,16特小,18小,25中,36大,45很大,64特别大 + * @return 以px为单位的字体大小 + */ + private fun getTextSize(n: Int): Float { + return when (n) { + 12 -> 12f + 16 -> 14f + 18 -> 17f + 25 -> 19f + 36 -> 21f + 45 -> 23f + 64 -> 25f + else -> 19f + } + } + private fun getType(s: Int): Int { // 类型(1从右至左滚动弹幕|6从左至右滚动弹幕|5顶端固定弹幕|4底端固定弹幕|7高级弹幕|8脚本弹幕) return when (s) { diff --git a/app/src/main/java/com/skyd/imomoe/view/fragment/HomeFragment.kt b/app/src/main/java/com/skyd/imomoe/view/fragment/HomeFragment.kt index 14869a63..ea6fabbe 100644 --- a/app/src/main/java/com/skyd/imomoe/view/fragment/HomeFragment.kt +++ b/app/src/main/java/com/skyd/imomoe/view/fragment/HomeFragment.kt @@ -9,14 +9,9 @@ import android.view.ViewStub import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.viewpager2.adapter.FragmentStateAdapter -import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator -import com.hjq.permissions.OnPermissionCallback -import com.hjq.permissions.Permission -import com.hjq.permissions.XXPermissions import com.skyd.imomoe.R import com.skyd.imomoe.databinding.FragmentHomeBinding import com.skyd.imomoe.model.DataSourceManager @@ -27,6 +22,7 @@ import com.skyd.imomoe.util.eventbus.EventBusSubscriber import com.skyd.imomoe.util.eventbus.MessageEvent import com.skyd.imomoe.util.eventbus.RefreshEvent import com.skyd.imomoe.util.eventbus.SelectHomeTabEvent +import com.skyd.imomoe.util.requestManageExternalStorage import com.skyd.imomoe.view.activity.* import com.skyd.imomoe.view.listener.dsl.addOnTabSelectedListener import com.skyd.imomoe.viewmodel.HomeViewModel @@ -92,18 +88,10 @@ class HomeFragment : BaseFragment(), EventBusSubscriber { ivHomeFragmentAnimeDownload.setOnClickListener { it.clickScale(0.8f, 70) - XXPermissions.with(this@HomeFragment).permission(Permission.MANAGE_EXTERNAL_STORAGE) - .request(object : OnPermissionCallback { - override fun onGranted(permissions: MutableList?, all: Boolean) { - startActivity(Intent(activity, AnimeDownloadActivity::class.java)) - } - - override fun onDenied(permissions: MutableList?, never: Boolean) { - super.onDenied(permissions, never) - "无存储权限,无法播放本地缓存视频".showToast(Toast.LENGTH_LONG) - } - } - ) + requestManageExternalStorage { + onGranted { startActivity(Intent(activity, AnimeDownloadActivity::class.java)) } + onDenied { "无存储权限,无法播放本地缓存视频".showToast(Toast.LENGTH_LONG) } + } } ivHomeFragmentFavorite.setOnClickListener { diff --git a/app/src/main/java/com/skyd/imomoe/view/listener/dsl/OnPermissionsCallback.kt b/app/src/main/java/com/skyd/imomoe/view/listener/dsl/OnPermissionsCallback.kt new file mode 100644 index 00000000..3869b2cf --- /dev/null +++ b/app/src/main/java/com/skyd/imomoe/view/listener/dsl/OnPermissionsCallback.kt @@ -0,0 +1,63 @@ +package com.skyd.imomoe.view.listener.dsl + +import com.hjq.permissions.XXPermissions + +fun XXPermissions.requestPermissions(init: OnPermissionsCallback.() -> Unit) { + val listener = OnPermissionsCallback() + listener.init() + this.request(listener) +} + +fun XXPermissions.requestSinglePermission(init: OnSinglePermissionCallback.() -> Unit) { + val listener = OnSinglePermissionCallback() + listener.init() + this.request(listener) +} + +private typealias Granted = (permissions: MutableList?, all: Boolean) -> Unit +private typealias Denied = (permissions: MutableList?, never: Boolean) -> Unit + +class OnPermissionsCallback : com.hjq.permissions.OnPermissionCallback { + private var granted: Granted? = null + private var denied: Denied? = null + + fun onGranted(granted: Granted?) { + this.granted = granted + } + + fun onDenied(denied: Denied?) { + this.denied = denied + } + + override fun onGranted(permissions: MutableList?, all: Boolean) { + granted?.invoke(permissions, all) + } + + override fun onDenied(permissions: MutableList?, never: Boolean) { + denied?.invoke(permissions, never) + } +} + +private typealias SingleGranted = () -> Unit +private typealias SingleDenied = (never: Boolean) -> Unit + +class OnSinglePermissionCallback : com.hjq.permissions.OnPermissionCallback { + private var singleGranted: SingleGranted? = null + private var singleDenied: SingleDenied? = null + + fun onGranted(singleGranted: SingleGranted?) { + this.singleGranted = singleGranted + } + + fun onDenied(singleDenied: SingleDenied?) { + this.singleDenied = singleDenied + } + + override fun onGranted(permissions: MutableList?, all: Boolean) { + singleGranted?.invoke() + } + + override fun onDenied(permissions: MutableList?, never: Boolean) { + singleDenied?.invoke(never) + } +} diff --git a/app/src/main/res/layout/activity_anime_detail.xml b/app/src/main/res/layout/activity_anime_detail.xml index 5dddfcf2..2d3b2dd9 100644 --- a/app/src/main/res/layout/activity_anime_detail.xml +++ b/app/src/main/res/layout/activity_anime_detail.xml @@ -19,16 +19,11 @@ android:layout_height="match_parent" android:background="@color/transparent_skin" /> - - diff --git a/app/src/main/res/layout/activity_favorite.xml b/app/src/main/res/layout/activity_favorite.xml index 9fda5935..8ef1382f 100644 --- a/app/src/main/res/layout/activity_favorite.xml +++ b/app/src/main/res/layout/activity_favorite.xml @@ -1,9 +1,9 @@ @@ -26,6 +26,7 @@ android:id="@+id/rv_favorite_activity" android:layout_width="match_parent" android:layout_height="match_parent" + android:clipToPadding="false" android:overScrollMode="never" android:paddingHorizontal="16dp" android:paddingTop="6dp" /> diff --git a/app/src/main/res/layout/layout_anime_video_player_land.xml b/app/src/main/res/layout/layout_anime_video_player_land.xml index 1e314f7c..8da8caa4 100644 --- a/app/src/main/res/layout/layout_anime_video_player_land.xml +++ b/app/src/main/res/layout/layout_anime_video_player_land.xml @@ -384,81 +384,14 @@ android:paddingHorizontal="12dp" android:paddingVertical="12dp"> - - - - - - - - - - - - - - - + android:textColor="@color/foreground_white_skin" + android:textSize="12sp" /> - - + android:textColor="@color/foreground_white_skin" + android:textSize="12sp" /> - + android:layout_marginTop="10dp"> - + - + android:orientation="horizontal"> + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 28b23e39..a0d5da1b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -192,4 +192,5 @@ 无效数据源文件后缀:%s 正在使用此数据源! 关闭 + 弹幕字号 diff --git a/doc/customdatasource/README.md b/doc/customdatasource/README.md index a88edae0..98f9e499 100644 --- a/doc/customdatasource/README.md +++ b/doc/customdatasource/README.md @@ -6,15 +6,15 @@ ## **快捷导航:大部分普通用户可以只看①-④** -## [①使用已有的自定义数据源](#一.使用已有的自定义数据源) +## [①使用已有的自定义数据源](#一-使用已有的自定义数据源) ## [②示例代码和ads包](#例子) -## [③删除数据源](#二.删除数据源) +## [③删除数据源](#二-删除数据源) -## [④恢复默认数据源](#三.恢复默认数据源) +## [④恢复默认数据源](#三-恢复默认数据源) -## [⑤自制数据源(高级)](#四.自制数据源) +## [⑤自制数据源(高级)](#四-自制数据源) ## 什么是自定义数据源? @@ -26,7 +26,7 @@ ## 如何使用自定义数据源功能? -### 一.使用已有的自定义数据源 +### 一 使用已有的自定义数据源 ### **>>已有的ads包可从[此处](#例子)找到<<** @@ -48,7 +48,7 @@ use_data_source_step_set -### 二.删除数据源 +### 二 删除数据源 #### 1.进入配置自定义数据源页面,长按列表中的数据源 @@ -58,7 +58,7 @@ use_data_source_step_delete -### 三.恢复默认数据源 +### 三 恢复默认数据源 #### 1.进入配置自定义数据源页面,点击右上角恢复按钮 @@ -68,7 +68,7 @@ use_data_source_step_reset_dialog -### 四.自制数据源 +### 四 自制数据源 自己编写ads包(熟悉**Kotlin**,最好能够了解Android的基础知识) @@ -135,6 +135,7 @@ com.skyd.imomoe.model.util.** com.skyd.imomoe.util.html.source.** com.skyd.imomoe.util.eventbus.** com.skyd.imomoe.util.Util +com.skyd.imomoe.util.ToastKt com.skyd.imomoe.bean.** com.skyd.imomoe.config.** org.jsoup.**