├── .gitignore
├── .idea
├── .gitignore
├── compiler.xml
├── gradle.xml
├── jarRepositories.xml
├── misc.xml
└── vcs.xml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── hyc
│ │ └── dd_monitor
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── iconfont.ttf
│ ├── java
│ │ └── com
│ │ │ └── hyc
│ │ │ └── dd_monitor
│ │ │ ├── MainActivity.kt
│ │ │ ├── MainApplication.kt
│ │ │ ├── models
│ │ │ ├── PlayerOptions.kt
│ │ │ └── UPInfo.kt
│ │ │ ├── utils
│ │ │ ├── CrashHandler.java
│ │ │ ├── RecordingUtils.kt
│ │ │ └── RoundImageTransform.kt
│ │ │ └── views
│ │ │ ├── DDLayout.kt
│ │ │ ├── DDPlayer.kt
│ │ │ ├── DanmuOptionsDialog.kt
│ │ │ ├── GlobalVolumeDialog.kt
│ │ │ ├── LayoutOptionsDialog.kt
│ │ │ ├── ScanQrActivity.kt
│ │ │ ├── UidImportDialog.kt
│ │ │ └── VolumeControlDialog.kt
│ └── res
│ │ ├── drawable-night
│ │ └── default_btn.xml
│ │ ├── drawable
│ │ ├── default_btn.xml
│ │ ├── default_btn_night.xml
│ │ ├── round.xml
│ │ └── round_teal.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── dd_layout_1.xml
│ │ ├── dd_layout_10.xml
│ │ ├── dd_layout_11.xml
│ │ ├── dd_layout_12.xml
│ │ ├── dd_layout_13.xml
│ │ ├── dd_layout_14.xml
│ │ ├── dd_layout_15.xml
│ │ ├── dd_layout_16.xml
│ │ ├── dd_layout_17.xml
│ │ ├── dd_layout_18.xml
│ │ ├── dd_layout_19.xml
│ │ ├── dd_layout_2.xml
│ │ ├── dd_layout_20.xml
│ │ ├── dd_layout_21.xml
│ │ ├── dd_layout_22.xml
│ │ ├── dd_layout_23.xml
│ │ ├── dd_layout_24.xml
│ │ ├── dd_layout_25.xml
│ │ ├── dd_layout_26.xml
│ │ ├── dd_layout_27.xml
│ │ ├── dd_layout_3.xml
│ │ ├── dd_layout_4.xml
│ │ ├── dd_layout_5.xml
│ │ ├── dd_layout_6.xml
│ │ ├── dd_layout_7.xml
│ │ ├── dd_layout_8.xml
│ │ ├── dd_layout_9.xml
│ │ ├── dd_player.xml
│ │ ├── dialog_danmu_options.xml
│ │ ├── dialog_global_volume.xml
│ │ ├── dialog_layout_options.xml
│ │ ├── dialog_scan_qr.xml
│ │ ├── dialog_uidimport.xml
│ │ ├── dialog_volume_single.xml
│ │ ├── item_danmu_text.xml
│ │ ├── item_layout_image.xml
│ │ └── list_item_up.xml
│ │ ├── menu
│ │ ├── danmu_clear.xml
│ │ ├── player_options.xml
│ │ ├── player_options_more.xml
│ │ ├── qn_menu.xml
│ │ ├── timer_menu.xml
│ │ └── up_item_card.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── layout1.png
│ │ ├── layout10.png
│ │ ├── layout11.png
│ │ ├── layout12.png
│ │ ├── layout13.png
│ │ ├── layout14.png
│ │ ├── layout15.png
│ │ ├── layout16.png
│ │ ├── layout17.png
│ │ ├── layout18.png
│ │ ├── layout19.png
│ │ ├── layout2.png
│ │ ├── layout20.png
│ │ ├── layout21.png
│ │ ├── layout22.png
│ │ ├── layout23.png
│ │ ├── layout24.png
│ │ ├── layout25.png
│ │ ├── layout26.png
│ │ ├── layout27.png
│ │ ├── layout3.png
│ │ ├── layout4.png
│ │ ├── layout5.png
│ │ ├── layout6.png
│ │ ├── layout7.png
│ │ ├── layout8.png
│ │ └── layout9.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-night
│ │ ├── colors.xml
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── com
│ └── hyc
│ └── dd_monitor
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── qrcode.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Josh Baker
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DD_Monitor-android-kotlin
2 |
3 | 
4 |
5 | ## 版本特性
6 | - 支持Android7.0以上。
7 | - 支持直播间id添加UP主。
8 | - 支持拖拽UP主卡片添加到直播窗口,支持窗口拖拽交换。
9 | - 支持横屏锁定。
10 | - 支持定时睡眠。
11 | - 支持16+11种布局方式。
12 |
13 | ## 使用须知
14 | - 使用时请注意宽带网速、流量消耗、电池电量、机身发热、系统卡顿等软硬件环境问题。
15 | - 本软件仅读取公开API数据,不涉及账号登录,欢迎查看源码进行监督。因此,本软件不支持弹幕互动、直播打赏等功能,若要使用请前往原版B站APP。
16 | - 直播流、UP主信息、以及个人公开的关注列表数据来自B站公开API,最终解释权归B站所有。
17 |
18 | ## 使用方式
19 | - 点击右上角“UP”按钮添加UP主,长按拖动到播放器窗口内。
20 | - 点击右上角"布局"按钮切换窗口布局。
21 | - 长按UP主卡片,然后拖动到窗口中。
22 | - 点击UP主卡片、或者点击下方UP主名字,弹出菜单选项,可跳转至UP主的直播间进行互动。
23 |
24 | ## TODO&BUG
25 | - [x] 音量的保存,以及交换窗口后同时交换音量大小
26 | - [x] 弹幕字体、视窗宽高
27 | - [x] 同传弹幕过滤
28 | - [x] 双击窗口全屏
29 | - [x] 前台开播提醒功能
30 | - [x] uid导入公开关注列表
31 | - [x] 显示SC弹幕
32 | - [x] 点击弹出删除弹幕选项(删除片哥)
33 | - [x] 扫码PC分享二维码
34 |
35 | ## 给开发者买杯奶茶
36 | [qrcode.png](qrcode.png)
37 |
38 | ## 开源依赖
39 | - [https://github.com/square/okhttp](https://github.com/square/okhttp)
40 | - [https://github.com/square/picasso](https://github.com/square/picasso)
41 | - [https://github.com/google/ExoPlayer](https://github.com/google/ExoPlayer)
42 |
43 | ## 相关链接
44 | - ios版:[https://gitee.com/hycong/dd-monitor-ios](https://gitee.com/hycong/dd-monitor-ios)
45 | - [https://gitee.com/zhimingshenjun/DD_Monitor_latest](https://gitee.com/zhimingshenjun/DD_Monitor_latest)
46 | - [https://github.com/lovelyyoshino/Bilibili-Live-API](https://github.com/lovelyyoshino/Bilibili-Live-API)
47 | - [https://github.com/MoyuScript/bilibili-api](https://github.com/MoyuScript/bilibili-api)
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdkVersion 29
8 |
9 | defaultConfig {
10 | applicationId "com.hyc.dd_monitor"
11 | minSdkVersion 24
12 | targetSdkVersion 29
13 | versionCode 1
14 | versionName "1.1.21"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | kotlinOptions {
30 | jvmTarget = '1.8'
31 | }
32 | }
33 |
34 | dependencies {
35 |
36 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
37 | implementation 'androidx.core:core-ktx:1.3.2'
38 | implementation 'androidx.appcompat:appcompat:1.2.0'
39 | implementation 'com.google.android.material:material:1.2.1'
40 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
41 |
42 | implementation("com.squareup.okhttp3:okhttp:4.9.0")
43 | implementation 'com.google.android.exoplayer:exoplayer:2.13.2'
44 | implementation 'com.squareup.picasso:picasso:2.71828'
45 | // implementation 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:1.0.0'
46 | implementation "com.google.code.gson:gson:2.8.6"
47 | implementation 'com.journeyapps:zxing-android-embedded:3.6.0'
48 | implementation("org.brotli:dec:0.1.2")
49 |
50 | testImplementation 'junit:junit:4.+'
51 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
52 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
53 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/hyc/dd_monitor/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.hyc.dd_monitor
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.hyc.dd_monitor", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/assets/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/congHu/DD_Monitor-android-kotlin/b835fcba296c2d3fae90a9de081b514cdc5c16fc/app/src/main/assets/iconfont.ttf
--------------------------------------------------------------------------------
/app/src/main/java/com/hyc/dd_monitor/MainApplication.kt:
--------------------------------------------------------------------------------
1 | package com.hyc.dd_monitor
2 |
3 | import android.app.Application
4 | import com.hyc.dd_monitor.utils.CrashHandler
5 |
6 | class MainApplication : Application() {
7 | override fun onCreate() {
8 | super.onCreate()
9 |
10 | // 崩溃日志保存
11 | val crashHandler = CrashHandler.getInstance()
12 | crashHandler.init(this)
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hyc/dd_monitor/models/PlayerOptions.kt:
--------------------------------------------------------------------------------
1 | package com.hyc.dd_monitor.models
2 |
3 | class PlayerOptions {
4 | var volume: Float = 1f
5 | var qn: Int = 80
6 | var isDanmuShow = true
7 | var danmuSize: Int = 13
8 | var danmuPosition: Int = 0
9 | var danmuWidth: Float = .2f
10 | var danmuHeight: Float = .8f
11 | var interpreterStyle: Int = 0
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hyc/dd_monitor/models/UPInfo.kt:
--------------------------------------------------------------------------------
1 | package com.hyc.dd_monitor.models
2 |
3 | class UPInfo {
4 | var faceImageUrl: String? = null
5 | var coverImageUrl: String? = null
6 |
7 | var uname: String? = null
8 | var title: String? = null
9 |
10 | var isLive: Boolean = false
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hyc/dd_monitor/utils/CrashHandler.java:
--------------------------------------------------------------------------------
1 | package com.hyc.dd_monitor.utils;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageInfo;
5 | import android.content.pm.PackageManager;
6 | import android.os.Build;
7 | import android.os.Environment;
8 | import android.util.Log;
9 |
10 | import java.io.BufferedWriter;
11 | import java.io.File;
12 | import java.io.FileWriter;
13 | import java.io.PrintWriter;
14 | import java.text.SimpleDateFormat;
15 | import java.util.Date;
16 | import java.util.concurrent.Executors;
17 |
18 | public class CrashHandler implements Thread.UncaughtExceptionHandler {
19 |
20 | private static final String TAG = "CrashHandler";
21 | private static final boolean DEBUG = true;
22 |
23 | private static final String PATH = Environment.getExternalStorageDirectory().getPath() + "/DDPlayer/Logs/";
24 |
25 | private static final String FILE_NAME = "crash";
26 | private static final String FILE_NAME_SUFFIX = ".txt";
27 |
28 | private static CrashHandler sInstance = new CrashHandler();
29 |
30 | private Context mContext;
31 |
32 | //私有构造器
33 | private CrashHandler() {
34 |
35 | }
36 |
37 | //单例模式
38 | public static CrashHandler getInstance() {
39 |
40 | return sInstance;
41 | }
42 |
43 | //初始化
44 | public void init(Context context) {
45 |
46 | Thread.setDefaultUncaughtExceptionHandler(this);
47 | mContext = context;
48 |
49 | }
50 |
51 | /**
52 | * 重写uncaughtException
53 | * @param t 发生Crash的线程
54 | * @param ex Throwale对象
55 | */
56 | @Override
57 | public void uncaughtException(Thread t, Throwable ex) {
58 | //处理逻辑需要开启一个子线程,用于文件的写入操作
59 | handleException(ex);
60 | //在程序关闭之前休眠2秒,以避免在文件写入的操作完
61 | //成。之前进程被杀死。
62 | //也可以考虑弹出对话框友好提示用户
63 | try {
64 | Thread.sleep(2000);
65 | } catch (InterruptedException e) {
66 | e.printStackTrace();
67 | }
68 |
69 | System.exit(0);
70 | // Process.killProcess(Process.myPid());
71 | }
72 |
73 | //处理异常
74 | private void handleException(final Throwable ex) {
75 | try {
76 | Executors.newSingleThreadExecutor().submit(new Runnable() {
77 | @Override
78 | public void run() {
79 | dumpExceptionToSDCard(ex);
80 | uploadExceptionToServer();
81 | }
82 | });
83 | } catch (Exception e) {
84 | e.printStackTrace();
85 | }
86 |
87 | }
88 |
89 | /**
90 | * 将异常信息上传至服务器
91 | */
92 | private void uploadExceptionToServer() {
93 |
94 |
95 | }
96 |
97 | /**
98 | * 将异常信息写入sd卡
99 | * @param ex
100 | */
101 | private void dumpExceptionToSDCard(Throwable ex) {
102 | //判断是否支持SD卡
103 | if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
104 | if (DEBUG) {
105 | Log.i(TAG, "sdcard unfind ,skip dump exception");
106 | return;
107 | }
108 | }
109 |
110 | File dir = new File(PATH);
111 | if (!dir.exists()) {
112 | dir.mkdirs();
113 | }
114 |
115 | long current = System.currentTimeMillis();
116 | String time = new SimpleDateFormat("yyyy-MM-dd").format(new Date(current));
117 |
118 | File file = new File(PATH + FILE_NAME + time + FILE_NAME_SUFFIX);
119 |
120 | try {
121 | PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
122 |
123 | pw.println(time);
124 | dumpPhoneInfo(pw);
125 | pw.println();
126 | //将抛出的异常信息写入到文件
127 | pw.println(ex.getMessage());
128 | ex.printStackTrace(pw);
129 | pw.close();
130 | } catch (Exception e) {
131 | Log.d(TAG, "dump Exception Exception" + e.getMessage());
132 | e.printStackTrace();
133 | }
134 |
135 | }
136 |
137 |
138 | /**
139 | *
140 | * 获取手机信息
141 | * @param pw 写入流
142 | * @throws PackageManager.NameNotFoundException 异常
143 | */
144 | private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
145 |
146 | PackageManager pm = mContext.getPackageManager();
147 | PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
148 |
149 | pw.print("App Version : ");
150 | pw.print(pi.versionName);
151 | Log.d(TAG,"name : "+pi.versionName);
152 | pw.print('_');
153 | pw.println(pi.versionCode);
154 |
155 | pw.print("OS Version : ");
156 | pw.print(Build.VERSION.RELEASE);
157 | pw.print("_");
158 | pw.println(Build.VERSION.SDK_INT );
159 |
160 | pw.print("Vendor : ");
161 | pw.println(Build.MANUFACTURER);
162 |
163 | pw.print("Model : ");
164 | pw.println(Build.MODEL);
165 | pw.print("Cpu ABI : ");
166 | pw.println(Build.CPU_ABI);
167 | }
168 |
169 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hyc/dd_monitor/utils/RecordingUtils.kt:
--------------------------------------------------------------------------------
1 | package com.hyc.dd_monitor.utils
2 |
3 | class RecordingUtils {
4 | companion object {
5 | fun byteString(len: Long) : String {
6 | if (len < 1024) return "${len}B"
7 | if (len < 1024*1024) return "${len/1024}K"
8 | if (len < 100*1024*1024) return String.format("%.1fM", len.toFloat()/1024f/1024f)
9 | return "${len/1024/1024}M"
10 | }
11 |
12 | fun minuteString(second: Long) : String {
13 | val min = second / 60
14 | val sec = second % 60
15 | val secStr = if (sec >= 10) "$sec" else "0$sec"
16 | return "$min:$secStr"
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hyc/dd_monitor/utils/RoundImageTransform.kt:
--------------------------------------------------------------------------------
1 | package com.hyc.dd_monitor.utils
2 |
3 | import android.graphics.*
4 | import com.squareup.picasso.Transformation
5 |
6 | class RoundImageTransform : Transformation {
7 | override fun transform(source: Bitmap?): Bitmap {
8 | val output = Bitmap.createBitmap(source!!.width, source.height, Bitmap.Config.ARGB_8888)
9 |
10 | val canvas = Canvas(output)
11 | val paint = Paint()
12 | paint.flags = Paint.ANTI_ALIAS_FLAG
13 |
14 | val rectF = RectF(Rect(0,0, output.width, output.height))
15 | canvas.drawRoundRect(rectF, (output.width/2).toFloat(),
16 | (output.height/2).toFloat(), paint)
17 |
18 | val paintImg = Paint()
19 | paintImg.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)
20 | canvas.drawBitmap(source, 0f, 0f, paintImg)
21 | source.recycle()
22 |
23 | return output
24 | }
25 |
26 | override fun key(): String {
27 | return "round"
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hyc/dd_monitor/views/DDLayout.kt:
--------------------------------------------------------------------------------
1 | package com.hyc.dd_monitor.views
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.LinearLayout
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.core.content.edit
10 |
11 | class DDLayout(context: Context?) : LinearLayout(context) {
12 | var layoutId : Int = 4
13 | set(value) {
14 | field = value
15 | reloadLayout()
16 | }
17 |
18 | var players: ArrayList
19 |
20 | var onCardDropListener: (() -> Unit)? = null
21 |
22 | var onPlayerClickListener: (() -> Unit)? = null
23 |
24 | var layoutPlayerCount = 0
25 |
26 | var fullScreenPlayerId: Int? = null
27 | var layoutBeforeFullScreen: Int? = null
28 |
29 | init {
30 | layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
31 | // this.layoutId = 2
32 |
33 |
34 | this.players = ArrayList()
35 |
36 | // 开始就把最多9个对象准备好,交换窗口时无缝切换
37 | for (i in 0..8) {
38 | val p = DDPlayer(context!!, i)
39 |
40 | // 处理窗口交换
41 | p.onDragAndDropListener = { drag, drop ->
42 | Log.d("drop", "drag drop $drag $drop")
43 | val dragViewId = context.resources.getIdentifier(
44 | "dd_layout_${drag+1}",
45 | "id",
46 | context.packageName
47 | )
48 | val dropViewId = context.resources.getIdentifier(
49 | "dd_layout_${drop+1}",
50 | "id",
51 | context.packageName
52 | )
53 | val dragView = stackview?.findViewById(dragViewId)
54 | val dropView = stackview?.findViewById(dropViewId)
55 | (players[drop].parent as ViewGroup?)?.removeView(players[drop])
56 | dragView?.addView(players[drop])
57 | (players[drag].parent as ViewGroup?)?.removeView(players[drag])
58 | dropView?.addView(players[drag])
59 | players[drag].playerId = drop
60 | players[drop].playerId = drag
61 |
62 | val temp = players[drag]
63 | players[drag] = players[drop]
64 | players[drop] = temp
65 |
66 | // 在post里面重新获取宽度调整工具栏,因为post之后获取的才是正确的宽度
67 | post {
68 | players[drag].adjustControlBar()
69 | players[drop].adjustControlBar()
70 | }
71 |
72 | // 交换音量设置
73 | val volume2 = players[drop].playerOptions.volume
74 | players[drop].playerOptions.volume = players[drag].playerOptions.volume
75 | players[drop].notifyPlayerOptionsChange()
76 | players[drag].playerOptions.volume = volume2
77 | players[drag].notifyPlayerOptionsChange()
78 |
79 | context.getSharedPreferences("sp", AppCompatActivity.MODE_PRIVATE).edit {
80 | this.putString("roomId$drop", players[drop].roomId).apply()
81 | this.putString("roomId$drag", players[drag].roomId).apply()
82 | }
83 | }
84 | p.onDoubleClickListener = {
85 | if (layoutBeforeFullScreen == null) {
86 | val target = players[it]
87 | players[it] = players[0]
88 | players[0] = target
89 | fullScreenPlayerId = it
90 | layoutBeforeFullScreen = layoutId
91 | layoutId = 1
92 | }else{
93 | val target = players[fullScreenPlayerId!!]
94 | players[fullScreenPlayerId!!] = players[0]
95 | players[0] = target
96 | layoutId = layoutBeforeFullScreen!!
97 | fullScreenPlayerId = null
98 | layoutBeforeFullScreen = null
99 | }
100 |
101 |
102 | }
103 | p.onCardDropListener = {
104 | onCardDropListener?.invoke()
105 | }
106 | p.onPlayerClickListener = {
107 | Log.d("ddclick", "ddclick")
108 | onPlayerClickListener?.invoke()
109 | }
110 | this.players.add(p)
111 | }
112 |
113 | // reloadLayout()
114 |
115 | context?.getSharedPreferences("sp", AppCompatActivity.MODE_PRIVATE)?.getInt("layout", 4)?.let {
116 | this.layoutId = it
117 | }
118 | }
119 |
120 | var stackview: View? = null
121 |
122 | // 重新刷新布局
123 | fun reloadLayout() {
124 | if (stackview != null) {
125 | removeView(stackview)
126 | }
127 |
128 | Log.d("ffffff", "dd_layout_$layoutId")
129 | val resId = context.resources.getIdentifier("dd_layout_$layoutId", "layout", context.packageName)
130 | stackview = inflate(context, resId, null)
131 | stackview?.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
132 | addView(stackview)
133 |
134 | layoutPlayerCount = 0
135 |
136 | for (i in 1..9) {
137 | val layoutId = context.resources.getIdentifier("dd_layout_$i", "id", context.packageName)
138 | val v = stackview?.findViewById(layoutId)
139 | val p = players[i-1]
140 | (p.parent as ViewGroup?)?.removeView(p)
141 | v?.addView(p)
142 |
143 | if (v != null) {
144 | // if (fullScreenPlayerId != null && p.roomId != null) { // 判断是否双击全屏触发的
145 | // p.roomId = p.roomId
146 | // }else
147 | if (p.roomId == null) {
148 | p.roomId = context?.getSharedPreferences("sp", AppCompatActivity.MODE_PRIVATE)?.getString("roomId${p.playerId}", null)
149 | }
150 |
151 | post {
152 | p.adjustControlBar()
153 | }
154 |
155 | layoutPlayerCount += 1
156 | }else{
157 | p.roomId = null
158 | }
159 | }
160 |
161 | }
162 |
163 |
164 | // 刷新全部
165 | fun refreshAll() {
166 | for (i in 0 until layoutPlayerCount) {
167 | val p = players[i]
168 | p.roomId = p.roomId
169 | }
170 | }
171 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hyc/dd_monitor/views/DanmuOptionsDialog.kt:
--------------------------------------------------------------------------------
1 | package com.hyc.dd_monitor.views
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Dialog
5 | import android.content.Context
6 | import android.os.Bundle
7 | import android.util.Log
8 | import android.widget.Button
9 | import android.widget.RadioGroup
10 | import android.widget.Switch
11 | import android.widget.TextView
12 | import androidx.appcompat.app.AppCompatActivity
13 | import com.google.gson.Gson
14 | import com.hyc.dd_monitor.R
15 | import com.hyc.dd_monitor.models.PlayerOptions
16 | import kotlin.math.roundToInt
17 |
18 | class DanmuOptionsDialog(context: Context, playerId: Int?) : Dialog(context) {
19 |
20 | var playerId: Int? = null
21 |
22 | var onDanmuOptionsChangeListener: ((options: PlayerOptions) -> Unit)? = null
23 |
24 | init {
25 | this.playerId = playerId
26 | }
27 |
28 | @SuppressLint("UseSwitchCompatOrMaterialCode")
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 | setContentView(R.layout.dialog_danmu_options)
32 |
33 | val title = findViewById(R.id.danmu_options_title)
34 |
35 | var playerOptions = PlayerOptions()
36 |
37 | if (playerId != null) {
38 | title.text = "窗口#${playerId!! +1}弹幕设置"
39 | context.getSharedPreferences("sp", AppCompatActivity.MODE_PRIVATE).getString("opts$playerId", "")?.let {
40 | try {
41 | Log.d("playeroptions", "load $it")
42 | playerOptions = Gson().fromJson(it, PlayerOptions::class.java)
43 | }catch (e:java.lang.Exception) {
44 |
45 | }
46 | }
47 | }else{
48 | title.text = "全局弹幕设置"
49 | }
50 |
51 |
52 |
53 | val switch = findViewById(R.id.isShow_switch)
54 | switch.isChecked = playerOptions.isDanmuShow
55 | switch.setOnCheckedChangeListener { compoundButton, b ->
56 | playerOptions.isDanmuShow = b
57 | onDanmuOptionsChangeListener?.invoke(playerOptions)
58 | }
59 |
60 | val pos = findViewById(R.id.danmu_position)
61 |
62 | when (playerOptions.danmuPosition) {
63 | 0 -> pos.check(R.id.danmu_position_lt)
64 | 1 -> pos.check(R.id.danmu_position_lb)
65 | 2 -> pos.check(R.id.danmu_position_rt)
66 | 3 -> pos.check(R.id.danmu_position_rb)
67 | }
68 |
69 | pos.setOnCheckedChangeListener { radioGroup, i ->
70 | when (i) {
71 | R.id.danmu_position_lt -> playerOptions.danmuPosition = 0
72 | R.id.danmu_position_lb -> playerOptions.danmuPosition = 1
73 | R.id.danmu_position_rt -> playerOptions.danmuPosition = 2
74 | R.id.danmu_position_rb -> playerOptions.danmuPosition = 3
75 | }
76 | onDanmuOptionsChangeListener?.invoke(playerOptions)
77 | }
78 |
79 | val fontsize = findViewById(R.id.fontsize_textview)
80 | fontsize.text = "${playerOptions.danmuSize}"
81 | findViewById