├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ ├── kikoplay.png
│ │ │ │ ├── kikoplays.png
│ │ │ │ ├── controlbg.xml
│ │ │ │ ├── titlebg.xml
│ │ │ │ ├── ic_play.xml
│ │ │ │ ├── ic_pause.xml
│ │ │ │ ├── ic_mark_blue.xml
│ │ │ │ ├── ic_mark_pink.xml
│ │ │ │ ├── ic_mark_red.xml
│ │ │ │ ├── ic_mark_green.xml
│ │ │ │ ├── ic_mark_orange.xml
│ │ │ │ ├── ic_mark_yellow.xml
│ │ │ │ ├── ic_fullscreen.xml
│ │ │ │ ├── ic_fullscreen_exit.xml
│ │ │ │ ├── ic_back.xml
│ │ │ │ ├── ic_expandable.xml
│ │ │ │ ├── textviewborder.xml
│ │ │ │ ├── ic_capture_start.xml
│ │ │ │ ├── ic_capture_stop.xml
│ │ │ │ ├── ic_launch.xml
│ │ │ │ ├── ic_danmu_show.xml
│ │ │ │ ├── ic_danmu_settings.xml
│ │ │ │ ├── ic_danmu_hide.xml
│ │ │ │ ├── ic_timeline.xml
│ │ │ │ ├── ic_connect.xml
│ │ │ │ ├── ic_capture_image.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ ├── anim
│ │ │ │ └── move_in_right.xml
│ │ │ ├── layout
│ │ │ │ ├── fragment_playlist.xml
│ │ │ │ ├── dialog_snippet.xml
│ │ │ │ ├── fragment_danmu.xml
│ │ │ │ ├── dialog_timeline.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── playlistitemlayout.xml
│ │ │ │ ├── sourceitemlayout.xml
│ │ │ │ ├── activity_play.xml
│ │ │ │ ├── playcontrol.xml
│ │ │ │ └── danmusetting.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── kikyou
│ │ │ │ └── kikoplay
│ │ │ │ ├── module
│ │ │ │ ├── AsyncCallBack.java
│ │ │ │ ├── ResponseHandler.java
│ │ │ │ ├── PlayListItem.java
│ │ │ │ ├── HttpUtil.java
│ │ │ │ ├── PlayListAdapter.java
│ │ │ │ └── DanmuPool.java
│ │ │ │ ├── KikoPlayApp.java
│ │ │ │ ├── PlaylistFragment.java
│ │ │ │ ├── DanmuFragment.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── PlayActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── kikyou
│ │ │ └── kikoplay
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── kikyou
│ │ └── kikoplay
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── screenshot
├── 1.jpg
├── 2.jpg
└── 3.jpg
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── vcs.xml
├── runConfigurations.xml
├── gradle.xml
├── codeStyles
│ └── Project.xml
└── misc.xml
├── .gitignore
├── README.md
├── gradle.properties
├── gradlew.bat
├── gradlew
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/screenshot/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/screenshot/1.jpg
--------------------------------------------------------------------------------
/screenshot/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/screenshot/2.jpg
--------------------------------------------------------------------------------
/screenshot/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/screenshot/3.jpg
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/kikoplay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/drawable/kikoplay.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/kikoplays.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/drawable/kikoplays.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KikoPlayProject/KikoPlay-Android-LAN/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/java/com/kikyou/kikoplay/module/AsyncCallBack.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay.module;
2 |
3 | public interface AsyncCallBack {
4 | void onResponse(int state);
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kikyou/kikoplay/module/ResponseHandler.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay.module;
2 |
3 | public interface ResponseHandler {
4 | void onResponse(byte[] result);
5 | }
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Aug 18 18:09:51 CST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/.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 | *.psd
15 | /app/build
16 | /app/release
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # KikoPlay-Android-LAN
2 | ---
3 | KikoPlay 局域网安卓端APP
4 |
5 | ## 使用方法
6 | 启动桌面版KikoPlay,打开局域网服务功能:
7 | 
8 |
9 | 确保手机和电脑处于同一个局域网中,打开KikoPlay安卓APP,点击右上角按钮设置KikoPlay服务地址:
10 | 
11 |
12 | 之后就可以访问播放列表并观看视频了:
13 |
14 |
15 | 
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 20sp
5 | 18sp
6 | 15sp
7 | 12sp
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/controlbg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/titlebg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kikyou/kikoplay/KikoPlayApp.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay;
2 |
3 | import android.app.Application;
4 |
5 | import com.kikyou.kikoplay.module.PlayListItem;
6 |
7 | import java.util.List;
8 |
9 | public class KikoPlayApp extends Application {
10 | List curPlayList;
11 | PlayListItem curPlayItem;
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pause.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mark_blue.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mark_pink.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mark_red.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mark_green.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mark_orange.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mark_yellow.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/move_in_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_fullscreen.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_fullscreen_exit.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/test/java/com/kikyou/kikoplay/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_back.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_expandable.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_playlist.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/textviewborder.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 | #FFFFFF
7 |
8 | #8a000000
9 | #00BCD4
10 | #C8000000
11 | #59000000
12 | #04C5B3
13 | #CEFFFFFF
14 |
15 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/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
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/kikyou/kikoplay/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.kikyou.kikoplay", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_capture_start.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_capture_stop.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_snippet.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launch.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_danmu_show.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
16 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_danmu_settings.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | compileOptions {
6 | targetCompatibility JavaVersion.VERSION_1_8
7 | }
8 | defaultConfig {
9 | applicationId "com.kikyou.kikoplay"
10 | minSdkVersion 19
11 | targetSdkVersion 28
12 | versionCode 2
13 | versionName "1.2"
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | vectorDrawables.useSupportLibrary = true
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | implementation fileTree(dir: 'libs', include: ['*.jar'])
27 | implementation 'com.android.support:appcompat-v7:28.0.0'
28 | implementation 'com.android.support:design:28.0.0'
29 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
30 | implementation 'com.google.android.exoplayer:exoplayer:2.9.6'
31 | implementation 'com.github.ctiao:DanmakuFlameMaster:0.9.25'
32 | implementation 'com.android.support:support-v4:28.0.0'
33 | testImplementation 'junit:junit:4.12'
34 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
35 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_danmu_hide.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_timeline.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.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 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_connect.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_capture_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_danmu.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
19 |
20 |
27 |
28 |
36 |
37 |
38 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_timeline.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
32 |
42 |
53 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
21 |
30 |
31 |
32 |
33 |
42 |
47 |
48 |
55 |
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kikyou/kikoplay/module/PlayListItem.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay.module;
2 |
3 | import com.kikyou.kikoplay.R;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | public class PlayListItem {
9 | private String title;
10 | private String animeName;
11 | private String media;
12 | private String pool;
13 | private int playTimeState;
14 | private int playTime;
15 | public enum Marker
16 | {
17 | M_RED, M_BLUE, M_GREEN, M_ORANGE, M_PINK, M_YELLOW, M_NONE
18 | }
19 | public static int MarkerIcon[] = {
20 | R.drawable.ic_mark_red, R.drawable.ic_mark_blue, R.drawable.ic_mark_green,
21 | R.drawable.ic_mark_orange, R.drawable.ic_mark_pink, R.drawable.ic_mark_yellow
22 | };
23 | private Marker marker;
24 | private PlayListItem parent;
25 | private List children;
26 |
27 | public PlayListItem(PlayListItem parent, String title, String media, String pool, String animeName){
28 | this.parent=parent;
29 | this.title=title;
30 | this.animeName=animeName;
31 | this.media=media;
32 | this.pool=pool;
33 | this.marker = Marker.M_NONE;
34 | if(media==null) {
35 | children=new ArrayList<>();
36 | }
37 | if(parent!=null){
38 | parent.addChild(this);
39 | }
40 | }
41 |
42 | private void addChild(PlayListItem child){
43 | children.add(child);
44 | }
45 |
46 | public PlayListItem getParent() {
47 | return parent;
48 | }
49 |
50 | public void setChildren(List children) {
51 | this.children = children;
52 | }
53 |
54 | public List getChildren() {
55 | return children;
56 | }
57 | public String getTitle() {
58 | return title;
59 | }
60 |
61 | public String getMedia() {
62 | return media;
63 | }
64 |
65 | public String getPool() {
66 | return pool;
67 | }
68 |
69 | public String getAnimeName() {return animeName;}
70 |
71 | public int getPlayTimeState() {
72 | return playTimeState;
73 | }
74 |
75 | public void setPlayTimeState(int playTimeState) {
76 | this.playTimeState = playTimeState;
77 | }
78 |
79 | public int getPlayTime() {
80 | return playTime;
81 | }
82 |
83 | public void setPlayTime(int playTime) {
84 | this.playTime = playTime;
85 | }
86 |
87 | public Marker getMarker() {return marker;}
88 |
89 | public void setMarker(Marker m) {this.marker = m;}
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | KikoPlay
3 | %1$d 个项目
4 | 未观看
5 | 已看完
6 | 播放到 %1$02d:%2$02d
7 | 播放
8 | 全屏
9 | 弹幕显示/隐藏
10 | 弹幕设置
11 | 发送弹幕
12 | 获取播放列表时出错
13 | 尚未设置KikoPlay局域网服务地址
14 | 播放列表是空的
15 | KikoPlay服务地址
16 | 再按一次退出
17 | 加载弹幕失败
18 | 按类型屏蔽
19 | 弹幕显示设置
20 | 顶部
21 | 滚动
22 | 底部
23 | 不透明度
24 | 弹幕字号
25 | 显示区域
26 | 弹幕速度
27 | 更新
28 | 正在更新
29 | 更新弹幕失败
30 | 获取信息失败
31 | 正在加载弹幕...
32 | 弹幕数量:%d
33 | %s - %s (%d)
34 | 延迟: %ds
35 | 设置延迟(s)
36 | 发送弹幕(%s)
37 | 开始(mm:ss)
38 | 时长(s)
39 | 添加
40 | 截图到资料库
41 | 截取片段到资料库
42 | 截图任务 %s 已发送
43 | 已选择截取开始位置[%s], 再次点击选择结束位置
44 | 保留片段音频
45 | 片段截取
46 | 开始: %s 结束: %s 时长: %ds
47 | 片段截取任务已发送
48 |
49 | - 1/4屏
50 | - 半屏
51 | - 3/4屏
52 | - 满屏
53 |
54 |
55 |
56 | Hello blank fragment
57 |
58 |
--------------------------------------------------------------------------------
/.idea/misc.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 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/playlistitemlayout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
35 |
36 |
48 |
49 |
57 |
58 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/sourceitemlayout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
25 |
26 |
42 |
43 |
55 |
56 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_play.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
18 |
24 |
28 |
32 |
33 |
34 |
38 |
44 |
50 |
51 |
52 |
59 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kikyou/kikoplay/PlaylistFragment.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.support.annotation.Nullable;
7 | import android.support.v4.app.Fragment;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.AdapterView;
12 | import android.widget.ListView;
13 |
14 | import com.kikyou.kikoplay.module.PlayListAdapter;
15 | import com.kikyou.kikoplay.module.PlayListItem;
16 |
17 |
18 | /**
19 | * A simple {@link Fragment} subclass.
20 | * Activities that contain this fragment must implement the
21 | * {@link PlaylistFragment.OnFragmentInteractionListener} interface
22 | * to handle interaction events.
23 | * Use the {@link PlaylistFragment#newInstance} factory method to
24 | * create an instance of this fragment.
25 | */
26 | public class PlaylistFragment extends Fragment {
27 |
28 | private ListView curListView;
29 | private PlayListAdapter curAdapter;
30 | private OnFragmentInteractionListener mListener;
31 |
32 | public PlaylistFragment() {
33 | // Required empty public constructor
34 | }
35 |
36 | // TODO: Rename and change types and number of parameters
37 | public static PlaylistFragment newInstance() {
38 | return new PlaylistFragment();
39 | }
40 |
41 | @Override
42 | public void onCreate(Bundle savedInstanceState) {
43 | super.onCreate(savedInstanceState);
44 | }
45 |
46 | @Override
47 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
48 | Bundle savedInstanceState) {
49 | // Inflate the layout for this fragment
50 | View root = inflater.inflate(R.layout.fragment_playlist, container, false);
51 | curListView = root.findViewById(R.id.curPlayList);
52 | return root;
53 | }
54 |
55 | @Override
56 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
57 | super.onActivityCreated(savedInstanceState);
58 | curListView.setAdapter(curAdapter);
59 | curListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
60 | @Override
61 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
62 | mListener.onPlayItemChanged((PlayListItem) curAdapter.getItem(position));
63 | }
64 | });
65 | }
66 |
67 | public void setAdapter(PlayListAdapter adapter){
68 | curAdapter=adapter;
69 | }
70 |
71 | @Override
72 | public void onAttach(Context context) {
73 | super.onAttach(context);
74 | if (context instanceof OnFragmentInteractionListener) {
75 | mListener = (OnFragmentInteractionListener) context;
76 | } else {
77 | throw new RuntimeException(context.toString()
78 | + " must implement OnFragmentInteractionListener");
79 | }
80 | }
81 |
82 | @Override
83 | public void onDetach() {
84 | super.onDetach();
85 | mListener = null;
86 | }
87 |
88 | public interface OnFragmentInteractionListener {
89 | // TODO: Update argument type and name
90 | void onPlayItemChanged(PlayListItem item);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kikyou/kikoplay/DanmuFragment.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.support.annotation.Nullable;
7 | import android.support.design.widget.Snackbar;
8 | import android.support.v4.app.Fragment;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.Button;
13 | import android.widget.ListView;
14 | import android.widget.TextView;
15 |
16 | import com.kikyou.kikoplay.module.AsyncCallBack;
17 | import com.kikyou.kikoplay.module.DanmuPool;
18 |
19 |
20 | /**
21 | * A simple {@link Fragment} subclass.
22 | * Activities that contain this fragment must implement the
23 | * {@link DanmuFragment.OnFragmentInteractionListener} interface
24 | * to handle interaction events.
25 | * Use the {@link DanmuFragment#newInstance} factory method to
26 | * create an instance of this fragment.
27 | */
28 | public class DanmuFragment extends Fragment {
29 |
30 | private OnFragmentInteractionListener mListener;
31 | private ListView sourcesView;
32 | private Button updateButton;
33 | private TextView danmuInfoTextView;
34 | private DanmuPool danmuPool;
35 | public DanmuFragment() {
36 | // Required empty public constructor
37 | }
38 |
39 |
40 | public static DanmuFragment newInstance() {
41 | return new DanmuFragment();
42 | }
43 |
44 | @Override
45 | public void onCreate(Bundle savedInstanceState) {
46 | super.onCreate(savedInstanceState);
47 | }
48 |
49 | @Override
50 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
51 | Bundle savedInstanceState) {
52 | // Inflate the layout for this fragment
53 | View root = inflater.inflate(R.layout.fragment_danmu, container, false);
54 | sourcesView=root.findViewById(R.id.curSourceList);
55 | updateButton=root.findViewById(R.id.updateDanmu);
56 | danmuInfoTextView=root.findViewById(R.id.danmuCountView);
57 | return root;
58 | }
59 |
60 | @Override
61 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
62 | super.onActivityCreated(savedInstanceState);
63 | sourcesView.setAdapter(danmuPool);
64 | updateButton.setOnClickListener(new View.OnClickListener() {
65 | @Override
66 | public void onClick(View v) {
67 | updateButton.setEnabled(false);
68 | sourcesView.setEnabled(false);
69 | updateButton.setText(getText(R.string.danmu_updating));
70 | danmuPool.updateDanmu(new AsyncCallBack() {
71 | @Override
72 | public void onResponse(int state) {
73 | updateButton.setEnabled(true);
74 | sourcesView.setEnabled(true);
75 | updateButton.setText(getText(R.string.danmu_update));
76 | updateCountInfo();
77 | mListener.showUpdateStatus(state);
78 | }
79 | });
80 | }
81 | });
82 | }
83 | public void setAdapter(DanmuPool danmuPool){
84 | this.danmuPool=danmuPool;
85 | }
86 | public void updateCountInfo(){
87 | danmuInfoTextView.setText(String.format(getString(R.string.danmu_count_desc), danmuPool.getDanmuCount()));
88 | }
89 | @Override
90 | public void onAttach(Context context) {
91 | super.onAttach(context);
92 | if (context instanceof OnFragmentInteractionListener) {
93 | mListener = (OnFragmentInteractionListener) context;
94 | } else {
95 | throw new RuntimeException(context.toString()
96 | + " must implement OnFragmentInteractionListener");
97 | }
98 | }
99 |
100 | @Override
101 | public void onDetach() {
102 | super.onDetach();
103 | mListener = null;
104 | }
105 |
106 | public interface OnFragmentInteractionListener {
107 | // TODO: Update argument type and name
108 | void showUpdateStatus(int state);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kikyou/kikoplay/module/HttpUtil.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay.module;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 |
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.Closeable;
8 | import java.io.DataOutputStream;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.net.HttpURLConnection;
12 | import java.net.URL;
13 | import java.util.concurrent.Executor;
14 | import java.util.concurrent.LinkedBlockingQueue;
15 | import java.util.concurrent.ThreadPoolExecutor;
16 | import java.util.concurrent.TimeUnit;
17 |
18 |
19 | public class HttpUtil {
20 | //获取当前设备的CPU数
21 | public static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
22 | //核心池大小设为CPU数加1
23 | private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
24 | //设置线程池的最大大小
25 | private static final int MAX_POOL_SIZE = 2 * CPU_COUNT + 1;
26 | //存活时间
27 | private static final long KEEP_ALIVE = 5L;
28 | //创建线程池对象
29 | public static final Executor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
30 | MAX_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingQueue());
31 |
32 | public static void getAsync(final String url, final ResponseHandler responseHandler, final int connectTimeout, final int readTimeout) {
33 | final Handler mHandler = new Handler(Looper.getMainLooper());
34 | //创建一个新的请求任务
35 | Runnable requestRunnable = new Runnable() {
36 | @Override
37 | public void run() {
38 | final byte[] result = get(url,connectTimeout,readTimeout);
39 | mHandler.post(new Runnable() {
40 | @Override
41 | public void run() {
42 | responseHandler.onResponse(result);
43 | }
44 | });
45 |
46 | }
47 | };
48 | threadPoolExecutor.execute(requestRunnable);
49 | }
50 | public static void postAsync(final String url, final String data, final ResponseHandler responseHandler, final int connectTimeout, final int readTimeout) {
51 | final Handler mHandler = new Handler(Looper.getMainLooper());
52 | //创建一个新的请求任务
53 | Runnable requestRunnable = new Runnable() {
54 | @Override
55 | public void run() {
56 | final byte[] result = post(url,data,connectTimeout,readTimeout);
57 | if(responseHandler==null) return;
58 | mHandler.post(new Runnable() {
59 | @Override
60 | public void run() {
61 | responseHandler.onResponse(result);
62 | }
63 | });
64 |
65 | }
66 | };
67 | threadPoolExecutor.execute(requestRunnable);
68 | }
69 | public static void getAsync(final String url, final ResponseHandler responseHandler){
70 | getAsync(url,responseHandler,8000,8000);
71 | }
72 | public static byte[] get(String urlString, int connectTimeout, int readTimeout) {
73 | HttpURLConnection urlConnection = null;
74 | try {
75 | URL url = new URL(urlString);
76 | urlConnection = (HttpURLConnection) url.openConnection();
77 | //设置请求方法
78 | urlConnection.setRequestMethod("GET");
79 | //设置超时时间
80 | urlConnection.setConnectTimeout(connectTimeout);
81 | urlConnection.setReadTimeout(readTimeout);
82 |
83 | //获取响应的状态码
84 | int responseCode = urlConnection.getResponseCode();
85 | if (responseCode == 200) {
86 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
87 | InputStream in = urlConnection.getInputStream();
88 | byte[] buffer = new byte[1024];
89 | int len;
90 | while ((len = in.read(buffer)) != -1) {
91 | bos.write(buffer, 0, len);
92 | }
93 | close(in);
94 | byte[] result = bos.toByteArray();
95 | close(bos);
96 | return result;
97 | } else {
98 | return null;
99 | }
100 | } catch (Exception e) {
101 | e.printStackTrace();
102 | } finally {
103 | if (urlConnection != null) {
104 | urlConnection.disconnect();
105 | }
106 | }
107 | return null;
108 | }
109 | public static byte[] post(String urlString, String data, int connectTimeout, int readTimeout){
110 | HttpURLConnection urlConnection = null;
111 | try {
112 | URL url = new URL(urlString);
113 | urlConnection = (HttpURLConnection) url.openConnection();
114 | urlConnection.setRequestMethod("POST");
115 | urlConnection.setConnectTimeout(connectTimeout);
116 | urlConnection.setReadTimeout(readTimeout);
117 | urlConnection.setDoOutput(true);
118 | urlConnection.setDoInput(true);
119 | DataOutputStream dos= new DataOutputStream(urlConnection.getOutputStream());
120 | dos.write(data.getBytes());
121 | close(dos);
122 |
123 | int responseCode = urlConnection.getResponseCode();
124 | if (responseCode == 200) {
125 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
126 | InputStream in = urlConnection.getInputStream();
127 | byte[] buffer = new byte[1024];
128 | int len;
129 | while ((len = in.read(buffer)) != -1) {
130 | bos.write(buffer, 0, len);
131 | }
132 | close(in);
133 | byte[] result = bos.toByteArray();
134 | close(bos);
135 | return result;
136 | } else {
137 | return null;
138 | }
139 | } catch (Exception e) {
140 | e.printStackTrace();
141 | } finally {
142 | if (urlConnection != null) {
143 | urlConnection.disconnect();
144 | }
145 | }
146 | return null;
147 | }
148 | private static void close(Closeable stream) {
149 | if (stream != null) {
150 | try {
151 | stream.close();
152 | } catch (IOException e) {
153 | e.printStackTrace();
154 | }
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/playcontrol.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
31 |
32 |
42 |
43 |
56 |
57 |
70 |
71 |
72 |
73 |
79 |
89 |
99 |
104 |
111 |
122 |
133 |
143 |
144 |
145 |
156 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kikyou/kikoplay/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay;
2 |
3 | import android.content.DialogInterface;
4 | import android.content.Intent;
5 | import android.content.SharedPreferences;
6 | import android.os.Handler;
7 | import android.support.v4.widget.SwipeRefreshLayout;
8 | import android.support.v7.app.AlertDialog;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.os.Bundle;
11 | import android.support.v7.widget.Toolbar;
12 | import android.view.GestureDetector;
13 | import android.view.MotionEvent;
14 | import android.view.View;
15 | import android.widget.AdapterView;
16 | import android.widget.EditText;
17 | import android.widget.ImageButton;
18 | import android.widget.ListView;
19 | import android.widget.TextView;
20 | import android.support.design.widget.Snackbar;
21 |
22 | import com.kikyou.kikoplay.module.HttpUtil;
23 | import com.kikyou.kikoplay.module.PlayListAdapter;
24 | import com.kikyou.kikoplay.module.PlayListItem;
25 | import com.kikyou.kikoplay.module.ResponseHandler;
26 |
27 | public class MainActivity extends AppCompatActivity {
28 |
29 | String kikoPlayServer;
30 | PlayListAdapter playListAdapter;
31 | boolean exitConfirm=false;
32 |
33 | TextView emptyTextView;
34 | ListView playListView;
35 | SwipeRefreshLayout listViewSwipe;
36 | ImageButton connectBtn;
37 | @Override
38 | protected void onCreate(Bundle savedInstanceState) {
39 | super.onCreate(savedInstanceState);
40 | setContentView(R.layout.activity_main);
41 | setSupportActionBar((Toolbar) findViewById(R.id.title_toolbar));
42 |
43 | SharedPreferences read = getSharedPreferences("config", MODE_PRIVATE);
44 | kikoPlayServer = read.getString("ServerAddress", "");
45 | playListAdapter=new PlayListAdapter(this);
46 | initViews();
47 | }
48 | void initViews(){
49 | emptyTextView=findViewById(R.id.emptyTextView);
50 | playListView=findViewById(R.id.playListView);
51 | playListView.setEmptyView(emptyTextView);
52 | listViewSwipe=findViewById(R.id.listSwipe);
53 | connectBtn=findViewById(R.id.connectBtn);
54 | playListView.setAdapter(playListAdapter);
55 |
56 | final SwipeRefreshLayout.OnRefreshListener refreshListener=new SwipeRefreshLayout.OnRefreshListener() {
57 | @Override
58 | public void onRefresh() {
59 | HttpUtil.getAsync("http://"+kikoPlayServer+"/api/playlist", new ResponseHandler() {
60 | @Override
61 | public void onResponse(byte[] result) {
62 | if(result!=null) {
63 | playListAdapter.setJsonData(new String(result));
64 | if(playListAdapter.getCount()==0) emptyTextView.setText(getText(R.string.listEmpyt));
65 | }
66 | else {
67 | emptyTextView.setText(getText(R.string.listError));
68 | }
69 | listViewSwipe.setRefreshing(false);
70 | }
71 | });
72 | }
73 | };
74 | listViewSwipe.setOnRefreshListener(refreshListener);
75 | if(kikoPlayServer.isEmpty()){
76 | emptyTextView.setText(getText(R.string.listNotConnect));
77 | }
78 | else{
79 | listViewSwipe.setRefreshing(true);
80 | refreshListener.onRefresh();
81 | }
82 | connectBtn.setOnClickListener(new View.OnClickListener() {
83 | @Override
84 | public void onClick(View v) {
85 | final EditText editText = new EditText(MainActivity.this);
86 | editText.setText(kikoPlayServer);
87 | AlertDialog.Builder inputDialog = new AlertDialog.Builder(MainActivity.this);
88 | inputDialog.setTitle(R.string.connectDialogTitle).setView(editText);
89 | inputDialog.setPositiveButton("OK",
90 | new DialogInterface.OnClickListener() {
91 | @Override
92 | public void onClick(DialogInterface dialog, int which) {
93 | kikoPlayServer=editText.getText().toString();
94 | SharedPreferences.Editor editor = getSharedPreferences("config", MODE_PRIVATE).edit();
95 | editor.putString("ServerAddress", kikoPlayServer);
96 | editor.apply();
97 | listViewSwipe.setRefreshing(true);
98 | refreshListener.onRefresh();
99 | }
100 | }).setNegativeButton("Cancel",null).show();
101 | }
102 | });
103 | playListView.setOnItemClickListener(new ListView.OnItemClickListener(){
104 | @Override
105 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
106 | PlayListItem item= (PlayListItem) playListAdapter.getItem(position);
107 | if(item.getMedia()==null){
108 | playListAdapter.setForward(position);
109 | }
110 | else{
111 | KikoPlayApp kApp=(KikoPlayApp) getApplication();
112 | kApp.curPlayList=playListAdapter.getCurLevelItems();
113 | kApp.curPlayItem=item;
114 | startActivityForResult(new Intent(MainActivity.this,PlayActivity.class),1);
115 | }
116 |
117 | }
118 | });
119 | final GestureDetector playListSwipeDetector=new GestureDetector(this, new GestureDetector.OnGestureListener() {
120 | @Override
121 | public boolean onDown(MotionEvent e) {
122 | return false;
123 | }
124 |
125 | @Override
126 | public void onShowPress(MotionEvent e) {
127 |
128 | }
129 |
130 | @Override
131 | public boolean onSingleTapUp(MotionEvent e) {
132 | return false;
133 | }
134 |
135 | @Override
136 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
137 | return false;
138 | }
139 |
140 | @Override
141 | public void onLongPress(MotionEvent e) {
142 |
143 | }
144 |
145 | @Override
146 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
147 | if (e2.getX() - e1.getX() > playListView.getWidth()/5) {
148 | playListAdapter.setBack();
149 | return true;
150 | }
151 | return false;
152 | }
153 | });
154 | playListView.setOnTouchListener(new View.OnTouchListener() {
155 | @Override
156 | public boolean onTouch(View v, MotionEvent event) {
157 | return playListSwipeDetector.onTouchEvent(event);
158 | }
159 | });
160 | }
161 | @Override
162 | public void onBackPressed() {
163 | if(!playListAdapter.setBack()) {
164 | if(exitConfirm){
165 | super.onBackPressed();
166 | }
167 | else {
168 | Snackbar.make(findViewById(R.id.mainLayout), getText(R.string.exitConfirmTip), Snackbar.LENGTH_LONG)
169 | .addCallback(new Snackbar.Callback(){
170 | @Override public void onDismissed(Snackbar transientBottomBar, int event) {
171 | super.onDismissed(transientBottomBar, event);
172 | exitConfirm=false;
173 | }
174 | }).show();
175 | exitConfirm = true;
176 | }
177 | }
178 | else {
179 | exitConfirm = false;
180 | }
181 | }
182 | @Override
183 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
184 | if(requestCode==1){
185 | if(resultCode==1) playListAdapter.notifyDataSetChanged();
186 | }
187 | }
188 |
189 | }
190 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kikyou/kikoplay/module/PlayListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay.module;
2 |
3 | import android.content.Context;
4 | import android.graphics.drawable.BitmapDrawable;
5 | import android.graphics.drawable.Drawable;
6 | import android.support.v4.content.ContextCompat;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.BaseAdapter;
10 | import android.widget.ImageView;
11 | import android.widget.TextView;
12 |
13 | import com.kikyou.kikoplay.R;
14 |
15 | import org.json.JSONArray;
16 | import org.json.JSONException;
17 | import org.json.JSONObject;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | public class PlayListAdapter extends BaseAdapter {
23 | private Context context;
24 | private PlayListItem root;
25 | private PlayListItem curCollection;
26 | private PlayListItem curPlayItem;
27 |
28 | public PlayListAdapter(Context context) {
29 | this.context = context;
30 | root=new PlayListItem(null,null,null,null, null);
31 | curCollection=root;
32 | }
33 |
34 | public void setJsonData(String json){
35 | root=new PlayListItem(null,null,null,null, null);
36 | try {
37 | JSONArray curArray = new JSONArray(json);
38 | buildList(curArray,root);
39 | curCollection=root;
40 | }
41 | catch (Exception e){
42 | e.printStackTrace();
43 | }
44 | notifyDataSetChanged();
45 | }
46 | public void setPlayList(List list){
47 | if(list!=null) {
48 | root.setChildren(list);
49 | notifyDataSetChanged();
50 | }
51 | }
52 | private void buildList(JSONArray curArray, PlayListItem parent) throws JSONException {
53 | for(int i = 0;i < curArray.length();i++){
54 | JSONObject itemObject = (JSONObject) curArray.get(i);
55 | if(itemObject.has("nodes")) {
56 | PlayListItem collection=new PlayListItem(parent,itemObject.getString("text"),null,null, null);
57 | if(itemObject.has("marker")) {
58 | collection.setMarker(PlayListItem.Marker.values()[itemObject.getInt("marker")]);
59 | }
60 | buildList(itemObject.getJSONArray("nodes"),collection);
61 | }
62 | else{
63 | PlayListItem item=new PlayListItem(parent,itemObject.getString("text"),
64 | itemObject.getString("mediaId"),itemObject.getString("danmuPool"),
65 | itemObject.getString("animeName"));
66 | item.setPlayTime(itemObject.getInt("playTime"));
67 | item.setPlayTimeState(itemObject.getInt("playTimeState"));
68 | if(itemObject.has("marker")) {
69 | item.setMarker(PlayListItem.Marker.values()[itemObject.getInt("marker")]);
70 | }
71 | }
72 | }
73 | }
74 |
75 | public List getCurLevelItems(){
76 | List list=new ArrayList<>();
77 | for (PlayListItem item:curCollection.getChildren()) {
78 | if(item.getMedia()!=null) list.add(item);
79 | }
80 | return list;
81 | }
82 |
83 | public void setForward(int pos){
84 | PlayListItem c=curCollection.getChildren().get(pos);
85 | if(c!=null && c.getChildren()!=null){
86 | curCollection=c;
87 | notifyDataSetChanged();
88 | }
89 | }
90 | public boolean setBack(){
91 | if(curCollection==root) return false;
92 | curCollection=curCollection.getParent();
93 | notifyDataSetChanged();
94 | return true;
95 | }
96 | public PlayListItem getCurPlayItem() {
97 | return curPlayItem;
98 | }
99 |
100 | public void setCurPlayItem(PlayListItem curPlayItem) {
101 | if(curPlayItem.getMedia()==null) return;
102 | this.curPlayItem = curPlayItem;
103 | notifyDataSetChanged();
104 | }
105 | public void setCurPlayTime(int time, int state){
106 | if(curPlayItem==null) return;
107 | curPlayItem.setPlayTime(time);
108 | curPlayItem.setPlayTimeState(state);
109 | notifyDataSetChanged();
110 | }
111 | public PlayListItem getNextItem(){
112 | List items;
113 | if(curPlayItem==null || curPlayItem.getMedia()==null){
114 | items=curCollection.getChildren();
115 | for (PlayListItem item:items) {
116 | if(item.getMedia()!=null) return item;
117 | }
118 | }
119 | else{
120 | items=curPlayItem.getParent().getChildren();
121 | int npos = items.indexOf(curPlayItem)+1;
122 | if(npos
2 |
6 |
7 |
18 |
19 |
29 |
30 |
42 |
43 |
56 |
57 |
68 |
69 |
76 |
77 |
88 |
89 |
103 |
104 |
115 |
116 |
129 |
130 |
144 |
145 |
156 |
157 |
170 |
171 |
185 |
186 |
197 |
198 |
211 |
212 |
226 |
227 |
238 |
239 |
252 |
253 |
254 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kikyou/kikoplay/module/DanmuPool.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay.module;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.content.DialogInterface;
6 | import android.content.SharedPreferences;
7 | import android.graphics.Color;
8 | import android.os.Bundle;
9 | import android.support.constraint.ConstraintLayout;
10 | import android.support.design.widget.Snackbar;
11 | import android.support.v4.content.ContextCompat;
12 | import android.support.v7.app.AlertDialog;
13 | import android.text.InputType;
14 | import android.util.Pair;
15 | import android.view.ContextMenu;
16 | import android.view.Display;
17 | import android.view.Gravity;
18 | import android.view.LayoutInflater;
19 | import android.view.MenuItem;
20 | import android.view.View;
21 | import android.view.ViewGroup;
22 | import android.view.Window;
23 | import android.view.WindowManager;
24 | import android.widget.AdapterView;
25 | import android.widget.ArrayAdapter;
26 | import android.widget.BaseAdapter;
27 | import android.widget.Button;
28 | import android.widget.EditText;
29 | import android.widget.ImageButton;
30 | import android.widget.ImageView;
31 | import android.widget.ListView;
32 | import android.widget.TextView;
33 |
34 | import com.google.android.exoplayer2.SimpleExoPlayer;
35 | import com.kikyou.kikoplay.MainActivity;
36 | import com.kikyou.kikoplay.PlayActivity;
37 | import com.kikyou.kikoplay.R;
38 |
39 | import org.json.JSONArray;
40 | import org.json.JSONException;
41 | import org.json.JSONObject;
42 |
43 | import java.util.ArrayList;
44 | import java.util.Collections;
45 | import java.util.Comparator;
46 | import java.util.HashMap;
47 | import java.util.List;
48 | import java.util.regex.Matcher;
49 | import java.util.regex.Pattern;
50 |
51 | import master.flame.danmaku.danmaku.model.BaseDanmaku;
52 | import master.flame.danmaku.danmaku.model.android.DanmakuContext;
53 | import master.flame.danmaku.danmaku.parser.BaseDanmakuParser;
54 | import master.flame.danmaku.ui.widget.DanmakuView;
55 |
56 | class DanmuComment {
57 | int time;
58 | int originTime;
59 | String text;
60 | int type;
61 | int color;
62 | int source;
63 | }
64 | class SourceInfo {
65 | String title;
66 | String scriptId, scriptData, scriptName;
67 | int id;
68 | int delay;
69 | int count;
70 | int duration;
71 | List> timeline=new ArrayList<>();
72 | void setTimeline(String timelineStr) {
73 | timeline.clear();
74 | if(timelineStr.trim().length()==0) return;
75 | String[] tls = timelineStr.trim().split(";");
76 | for (String tl:tls) {
77 | int start = Integer.parseInt(tl.split(" ")[0]);
78 | int duration = Integer.parseInt(tl.split(" ")[1]);
79 | timeline.add(new Pair<>(start, duration));
80 | }
81 | Collections.sort(timeline, new Comparator>() {
82 | @Override
83 | public int compare(Pair o1, Pair o2) {
84 | return o1.first.compareTo(o2.first);
85 | }
86 | });
87 | }
88 | void addTimelineItem(int start, int duration){
89 | for(Pair pair:timeline){
90 | if(pair.first==start) return;
91 | }
92 | timeline.add(new Pair<>(start, duration));
93 | Collections.sort(timeline, new Comparator>() {
94 | @Override
95 | public int compare(Pair o1, Pair o2) {
96 | return o1.first.compareTo(o2.first);
97 | }
98 | });
99 | }
100 | void removeTimeline(int pos){
101 | if(pos<0||pos>=timeline.size()) return;
102 | timeline.remove(pos);
103 | }
104 | String getTimeline(){
105 | StringBuilder builder=new StringBuilder();
106 | for(Pair pair:timeline){
107 | builder.append(pair.first);
108 | builder.append(' ');
109 | builder.append(pair.second);
110 | builder.append(';');
111 | }
112 | return builder.toString();
113 | }
114 | }
115 | public class DanmuPool extends BaseAdapter {
116 | private Context context;
117 | private List sources;
118 | private HashMap sourcesHash;
119 | private List comments;
120 | private SimpleExoPlayer player;
121 | private DanmakuView danmakuView;
122 | private DanmakuContext danmakuContext;
123 | private BaseDanmakuParser parser;
124 | private String kikoPlayServer;
125 | private String poolId;
126 | private List launchScriptIds;
127 |
128 | public DanmuPool(SimpleExoPlayer player, DanmakuView danmakuView, DanmakuContext danmakuContext, BaseDanmakuParser parser, Context context, String serverAddress){
129 | this.player=player;
130 | this.danmakuView=danmakuView;
131 | this.danmakuContext=danmakuContext;
132 | this.parser=parser;
133 | this.context=context;
134 | kikoPlayServer=serverAddress;
135 | sources=new ArrayList<>();
136 | launchScriptIds= new ArrayList<>();
137 | sourcesHash=new HashMap<>();
138 | comments=new ArrayList<>();
139 | }
140 | public int getDanmuCount(){return comments.size();}
141 | @Override
142 | public int getCount() {
143 | return sources.size();
144 | }
145 |
146 | @Override
147 | public Object getItem(int position) {
148 | return sources.get(position);
149 | }
150 |
151 | @Override
152 | public long getItemId(int position) {
153 | return position;
154 | }
155 |
156 | @Override
157 | public View getView(int position, View convertView, ViewGroup parent) {
158 | ViewHolder viewHolder;
159 | final SourceInfo sourceInfo = sources.get(position);
160 | if (convertView == null) {
161 | viewHolder = new ViewHolder();
162 | convertView = View.inflate(context, R.layout.sourceitemlayout,null);
163 | viewHolder.title = convertView.findViewById(R.id.source_title);
164 | viewHolder.url = convertView.findViewById(R.id.source_url);
165 | viewHolder.delay = convertView.findViewById(R.id.delay_info);
166 | viewHolder.edit_timeline = convertView.findViewById(R.id.edit_timeline);
167 | viewHolder.delay.setOnClickListener(new View.OnClickListener() {
168 | int srcId;
169 | {
170 | srcId=sourceInfo.id;
171 | }
172 | @Override
173 | public void onClick(View v) {
174 | showDelayDialog(srcId);
175 | }
176 | });
177 | viewHolder.edit_timeline.setOnClickListener(new View.OnClickListener() {
178 | int srcId;
179 | {
180 | srcId=sourceInfo.id;
181 | }
182 | @Override
183 | public void onClick(View v) { showTimelineDialog(srcId); }
184 | });
185 | convertView.setTag(viewHolder);
186 | }else{
187 | viewHolder = (ViewHolder) convertView.getTag();
188 | }
189 |
190 | viewHolder.title.setText(String.format(context.getString(R.string.source_title_info), sourceInfo.scriptName, sourceInfo.title,sourceInfo.count));
191 | viewHolder.url.setText(formatTime(sourceInfo.duration*1000));
192 | viewHolder.delay.setText(String.format(context.getString(R.string.source_delay_info),sourceInfo.delay/1000));
193 | return convertView;
194 | }
195 | private void showDelayDialog(final int srcId) {
196 | SourceInfo sourceInfo = sourcesHash.get(srcId);
197 | if(sourceInfo==null) return;
198 | final EditText editText = new EditText(context);
199 | editText.setText(Integer.toString(sourceInfo.delay/1000));
200 | AlertDialog.Builder inputDialog = new AlertDialog.Builder(context);
201 | inputDialog.setTitle(R.string.delay_dialog_title).setView(editText);
202 | inputDialog.setPositiveButton("OK",
203 | new DialogInterface.OnClickListener() {
204 | @Override
205 | public void onClick(DialogInterface dialog, int which) {
206 | try{
207 | String delayStr=editText.getText().toString();
208 | if(delayStr.isEmpty()) delayStr="0";
209 | int newDelay = Integer.parseInt(delayStr);
210 | setDelay(srcId, newDelay*1000);
211 | }
212 | catch (NumberFormatException e){
213 | e.printStackTrace();
214 | }
215 |
216 | }
217 | }).setNegativeButton("Cancel",null).show();
218 | }
219 | private void showTimelineDialog(final int srcId){
220 | final SourceInfo sourceInfo = sourcesHash.get(srcId);
221 | if(sourceInfo==null) return;
222 | TimelineDialog timelineDialog=new TimelineDialog(sourceInfo,context);
223 | timelineDialog.show();
224 | }
225 |
226 | private String formatTime(int mSec){
227 | int cs=(mSec>=0?mSec:-mSec)/1000;
228 | int cmin=cs/60;
229 | int cls=cs-cmin*60;
230 | return String.format("%s%02d:%02d",(mSec<0?"-":""),cmin,cls);
231 | }
232 | private void setDelay(DanmuComment comment){
233 | SourceInfo srcInfo = sourcesHash.get(comment.source);
234 | if(srcInfo==null) {
235 | comment.time=comment.originTime;
236 | return;
237 | }
238 | int delay=0;
239 | for(Pair t: srcInfo.timeline){
240 | if(comment.originTime>t.first) delay+=t.second;
241 | }
242 | delay+=srcInfo.delay;
243 | comment.time=comment.originTime+delay<0?comment.originTime:comment.originTime+delay;
244 | }
245 | private void updateDanmakuView() {
246 | danmakuView.removeAllDanmakus(true);
247 | for(DanmuComment comment:comments){
248 | BaseDanmaku danmu = danmakuContext.mDanmakuFactory.createDanmaku(comment.type, danmakuContext);
249 | danmu.text = comment.text;
250 | danmu.setTime(comment.time);
251 | danmu.textColor = comment.color;
252 | int cv = (danmu.textColor & 0xff) + ((danmu.textColor >> 8) & 0xff) + ((danmu.textColor >> 16) & 0xff);
253 | danmu.textShadowColor = cv / 3 <= 128 ? Color.WHITE : Color.BLACK;
254 | danmu.textSize = 25 * (parser.getDisplayer().getDensity() - 0.5f);
255 | danmakuView.addDanmaku(danmu);
256 | }
257 | danmakuView.seekTo(player.getCurrentPosition());
258 | if(!player.getPlayWhenReady()) {
259 | danmakuView.pause();
260 | }
261 | }
262 | private void setPool(JSONObject danmuObj) throws JSONException {
263 | JSONArray sourceArray = danmuObj.getJSONArray("source");
264 | JSONArray danmuArray = danmuObj.getJSONArray("comment");
265 | sources.clear();
266 | sourcesHash.clear();
267 | comments.clear();
268 | for(int i=0;i> 8) & 0xff) + ((danmu.textColor >> 16) & 0xff);
326 | danmu.textShadowColor = cv / 3 <= 128 ? Color.WHITE : Color.BLACK;
327 | danmu.textSize = 25 * (parser.getDisplayer().getDensity() - 0.5f);
328 | danmakuView.addDanmaku(danmu);
329 | }
330 | }
331 | }
332 | private void setTimeline(SourceInfo srcInfo){
333 | for(DanmuComment comment:comments){
334 | if(comment.source==srcInfo.id)
335 | setDelay(comment);
336 | }
337 | updateDanmakuView();
338 | notifyDataSetChanged();
339 |
340 | JSONObject obj=new JSONObject();
341 | try {
342 | obj.put("danmuPool", poolId);
343 | obj.put("timeline", srcInfo.getTimeline());
344 | obj.put("source", srcInfo.id);
345 | HttpUtil.postAsync("http://" + kikoPlayServer + "/api/updateTimeline",obj.toString(),null,5000,5000);
346 | }
347 | catch (Exception e){
348 | e.printStackTrace();
349 | }
350 | }
351 | private void setDelay(int srcId, int newDelay){
352 | SourceInfo srcInfo = sourcesHash.get(srcId);
353 | if(srcInfo==null) return;
354 | if(srcInfo.delay==newDelay) return;
355 | srcInfo.delay=newDelay; //ms
356 | for(DanmuComment comment:comments){
357 | if(comment.source==srcId)
358 | setDelay(comment);
359 | }
360 | updateDanmakuView();
361 | notifyDataSetChanged();
362 |
363 | JSONObject obj=new JSONObject();
364 | try {
365 | obj.put("danmuPool", poolId);
366 | obj.put("delay", newDelay);
367 | obj.put("source", srcId);
368 | HttpUtil.postAsync("http://" + kikoPlayServer + "/api/updateDelay",obj.toString(),null,5000,5000);
369 | }
370 | catch (Exception e){
371 | e.printStackTrace();
372 | }
373 | }
374 | public void loadDanmu(final String pid, final AsyncCallBack callBack){
375 | if(pid.isEmpty()){
376 | callBack.onResponse(-2);
377 | return;
378 | }
379 | HttpUtil.getAsync("http://" + kikoPlayServer + "/api/danmu/full/?id=" + pid, new ResponseHandler() {
380 | @Override
381 | public void onResponse(byte[] result) {
382 | if (result != null) {
383 | try {
384 | JSONObject responseObj=new JSONObject(new String(result));
385 | poolId=pid;
386 | setPool(responseObj);
387 | callBack.onResponse(0);
388 | } catch (Exception e) {
389 | e.printStackTrace();
390 | callBack.onResponse(-1);
391 | }
392 | } else {
393 | callBack.onResponse(-1);
394 | }
395 | }
396 | },5000,15*1000);
397 | }
398 | public void updateDanmu(final AsyncCallBack callBack){
399 | if(poolId.isEmpty()){
400 | callBack.onResponse(-2);
401 | return;
402 | }
403 | HttpUtil.getAsync("http://" + kikoPlayServer + "/api/danmu/full/?id=" + poolId + "&update=true", new ResponseHandler() {
404 | String pid;
405 | {
406 | pid=poolId;
407 | }
408 | @Override
409 | public void onResponse(byte[] result) {
410 | if(!poolId.equals(pid)) {
411 | callBack.onResponse(-1);
412 | return;
413 | }
414 | if (result != null) {
415 | try {
416 | JSONObject obj=new JSONObject(new String(result));
417 | if(obj.getBoolean("update")) {
418 | JSONArray danmuArray = obj.getJSONArray("comment");
419 | addDanmu(danmuArray, true);
420 | callBack.onResponse(0);
421 | }
422 | } catch (Exception e) {
423 | e.printStackTrace();
424 | callBack.onResponse(-1);
425 | }
426 | } else {
427 | callBack.onResponse(-1);
428 | }
429 | }
430 | },5000,60*1000);
431 | }
432 | public boolean canLaunch(){
433 | return launchScriptIds.size()>0;
434 | }
435 | public void launch(int time, String text){
436 | if(text.isEmpty() || poolId.isEmpty()) return;
437 | try {
438 | JSONObject obj = new JSONObject();
439 | obj.put("danmuPool", poolId);
440 | obj.put("time", time);
441 | obj.put("launchScripts", new JSONArray(launchScriptIds));
442 | obj.put("text", text);
443 | HttpUtil.postAsync("http://" + kikoPlayServer + "/api/danmu/launch", obj.toString(), null, 5000, 5000);
444 |
445 | BaseDanmaku danmu = danmakuContext.mDanmakuFactory.createDanmaku(1, danmakuContext);
446 | danmu.text = text;
447 | danmu.setTime(time);
448 | danmu.textColor = 0xffffff;
449 | int cv = (danmu.textColor & 0xff) + ((danmu.textColor >> 8) & 0xff) + ((danmu.textColor >> 16) & 0xff);
450 | danmu.textShadowColor = Color.BLACK;
451 | danmu.textSize = 25 * (parser.getDisplayer().getDensity() - 0.5f);
452 | danmakuView.addDanmaku(danmu);
453 |
454 | }catch (Exception e){
455 | e.printStackTrace();
456 | }
457 | }
458 | class ViewHolder{
459 | TextView title;
460 | TextView url;
461 | TextView delay;
462 | ImageButton edit_timeline;
463 | }
464 | class TimelineDialog extends Dialog implements View.OnClickListener {
465 | EditText timeline_start;
466 | EditText timeline_duration;
467 | Button timeline_add;
468 | ListView timeline_view;
469 | SourceInfo srcInfo;
470 | ArrayAdapter timeline_adapter;
471 | boolean sourceChanged=false;
472 |
473 |
474 | public TimelineDialog(SourceInfo srcInfo, Context context) {
475 | super(context);
476 | this.srcInfo=srcInfo;
477 | }
478 |
479 | @Override
480 | public void dismiss() {
481 | super.dismiss();
482 | if(sourceChanged) setTimeline(srcInfo);
483 | }
484 |
485 | @Override
486 | protected void onCreate(Bundle savedInstanceState) {
487 | super.onCreate(savedInstanceState);
488 | setContentView(R.layout.dialog_timeline);
489 |
490 | timeline_start=findViewById(R.id.timeline_start);
491 | timeline_duration=findViewById(R.id.timeline_duration);
492 | timeline_add=findViewById(R.id.timeline_add);
493 | timeline_view=findViewById(R.id.timeline_list);
494 | timeline_adapter=new ArrayAdapter<>(context,android.R.layout.simple_expandable_list_item_1);
495 | timeline_add.setOnClickListener(this);
496 | timeline_start.setText(formatTime((int)player.getCurrentPosition()));
497 | timeline_view.setAdapter(timeline_adapter);
498 | refreshAdapter();
499 | registerForContextMenu(timeline_view);
500 | timeline_view.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
501 | @Override
502 | public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
503 | menu.add(0,0,0,"删除").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
504 | @Override
505 | public boolean onMenuItemClick(MenuItem item) {
506 | if(item.getItemId()==0){
507 | AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
508 | srcInfo.removeTimeline(info.position);
509 | refreshAdapter();
510 | sourceChanged=true;
511 | }
512 | return true;
513 | }
514 | });
515 | }
516 | });
517 | }
518 |
519 | private void refreshAdapter(){
520 | timeline_adapter.clear();
521 | for(Pair p:srcInfo.timeline){
522 | timeline_adapter.add(String.format("开始:%s 时长:%s",formatTime(p.first),formatTime(p.second)));
523 | }
524 | }
525 | @Override
526 | public void onClick(View v) {
527 | if(v==timeline_add){
528 | try{
529 | String start_txt=timeline_start.getText().toString();
530 | Pattern r=Pattern.compile("(\\d+):(\\d+)");
531 | Matcher m=r.matcher(start_txt);
532 | if(!m.matches()) return;
533 | if(m.end()-m.start()!=start_txt.length()) return;
534 | int start=Integer.parseInt(m.group(1))*60+Integer.parseInt(m.group(2));
535 | start*=1000;
536 | int duration=Integer.parseInt(timeline_duration.getText().toString())*1000;
537 | if(duration==0) return;
538 | srcInfo.addTimelineItem(start,duration);
539 | refreshAdapter();
540 | sourceChanged=true;
541 | }
542 | catch (Exception e){
543 | e.printStackTrace();
544 | }
545 |
546 | }
547 | }
548 | }
549 | }
550 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kikyou/kikoplay/PlayActivity.java:
--------------------------------------------------------------------------------
1 | package com.kikyou.kikoplay;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.DialogInterface;
6 | import android.content.SharedPreferences;
7 | import android.content.pm.ActivityInfo;
8 | import android.content.res.Configuration;
9 | import android.graphics.Color;
10 | import android.media.AudioManager;
11 | import android.net.Uri;
12 | import android.os.Build;
13 | import android.os.Handler;
14 | import android.os.Message;
15 | import android.provider.Settings;
16 | import android.support.annotation.Nullable;
17 | import android.support.constraint.ConstraintLayout;
18 | import android.support.design.widget.Snackbar;
19 | import android.support.design.widget.TabLayout;
20 | import android.support.v4.app.Fragment;
21 | import android.support.v4.app.FragmentPagerAdapter;
22 | import android.support.v4.content.ContextCompat;
23 | import android.support.v4.view.ViewPager;
24 | import android.support.v7.app.AlertDialog;
25 | import android.support.v7.app.AppCompatActivity;
26 | import android.os.Bundle;
27 | import android.util.DisplayMetrics;
28 | import android.util.Log;
29 | import android.view.GestureDetector;
30 | import android.view.LayoutInflater;
31 | import android.view.MotionEvent;
32 | import android.view.OrientationEventListener;
33 | import android.view.SurfaceView;
34 | import android.view.View;
35 | import android.view.ViewGroup;
36 | import android.view.WindowManager;
37 | import android.view.animation.AnimationUtils;
38 | import android.widget.AdapterView;
39 | import android.widget.Button;
40 | import android.widget.CheckBox;
41 | import android.widget.EditText;
42 | import android.widget.FrameLayout;
43 | import android.widget.ImageButton;
44 | import android.widget.ListView;
45 | import android.widget.SeekBar;
46 | import android.widget.TextView;
47 |
48 | import com.google.android.exoplayer2.C;
49 | import com.google.android.exoplayer2.ExoPlaybackException;
50 | import com.google.android.exoplayer2.ExoPlayerFactory;
51 | import com.google.android.exoplayer2.Format;
52 | import com.google.android.exoplayer2.Player;
53 | import com.google.android.exoplayer2.SimpleExoPlayer;
54 | import com.google.android.exoplayer2.source.ExtractorMediaSource;
55 | import com.google.android.exoplayer2.source.MediaSource;
56 | import com.google.android.exoplayer2.source.MergingMediaSource;
57 | import com.google.android.exoplayer2.source.SingleSampleMediaSource;
58 | import com.google.android.exoplayer2.text.Cue;
59 | import com.google.android.exoplayer2.text.TextRenderer;
60 | import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
61 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
62 | import com.google.android.exoplayer2.trackselection.TrackSelection;
63 | import com.google.android.exoplayer2.trackselection.TrackSelector;
64 | import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
65 | import com.google.android.exoplayer2.ui.SubtitleView;
66 | import com.google.android.exoplayer2.upstream.DataSource;
67 | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
68 | import com.google.android.exoplayer2.util.MimeTypes;
69 | import com.google.android.exoplayer2.util.Util;
70 | import com.google.android.exoplayer2.video.VideoListener;
71 | import com.kikyou.kikoplay.module.AsyncCallBack;
72 | import com.kikyou.kikoplay.module.DanmuPool;
73 | import com.kikyou.kikoplay.module.HttpUtil;
74 | import com.kikyou.kikoplay.module.PlayListAdapter;
75 | import com.kikyou.kikoplay.module.PlayListItem;
76 | import com.kikyou.kikoplay.module.ResponseHandler;
77 |
78 | import org.json.JSONArray;
79 | import org.json.JSONException;
80 | import org.json.JSONObject;
81 |
82 | import java.lang.ref.WeakReference;
83 | import java.util.HashMap;
84 | import java.util.List;
85 | import java.util.Timer;
86 | import java.util.TimerTask;
87 |
88 | import master.flame.danmaku.controller.DrawHandler;
89 | import master.flame.danmaku.danmaku.model.BaseDanmaku;
90 | import master.flame.danmaku.danmaku.model.DanmakuTimer;
91 | import master.flame.danmaku.danmaku.model.IDanmakus;
92 | import master.flame.danmaku.danmaku.model.IDisplayer;
93 | import master.flame.danmaku.danmaku.model.android.DanmakuContext;
94 | import master.flame.danmaku.danmaku.model.android.Danmakus;
95 | import master.flame.danmaku.danmaku.parser.BaseDanmakuParser;
96 | import master.flame.danmaku.ui.widget.DanmakuView;
97 |
98 | public class PlayActivity extends AppCompatActivity implements PlaylistFragment.OnFragmentInteractionListener,DanmuFragment.OnFragmentInteractionListener {
99 | String kikoPlayServer;
100 | PlayListAdapter curAdapter;
101 | List curList;
102 | PlayListItem selectedItem;
103 | DanmuPool pool;
104 | AsyncCallBack loadDanmuCallBack;
105 |
106 | TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
107 | TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
108 | SimpleExoPlayer player;
109 | AudioManager mAudioManager;
110 |
111 | FrameLayout surfaceFrame;
112 | DanmakuContext danmuContext;
113 | SurfaceView videoSurface;
114 | SubtitleView subtitleView;
115 | AspectRatioFrameLayout surfaceAspect;
116 | DanmakuView danmuView;
117 | View playControl;
118 | ImageButton playPause, fullScreen, danmuSetting, danmuVisible, danmuLaunch, back,
119 | captureImage, captureSnippet;
120 | SeekBar videoSeekBar;
121 | TextView timeText,seekTipText,titleText;
122 | ConstraintLayout danmuSettingView,danmuSettingLayout;
123 | TextView blockTopView,blockRollView,blockBottomView;
124 | SeekBar danmuOpacitySeek,danmuSizeSeek,danmuSpeedSeek,danmuRegionSeek;
125 | TextView danmuOpacityVal,danmuSizeVal,danmuSpeedVal,danmuRegionVal;
126 |
127 | PlaylistFragment playlistFragment;
128 | DanmuFragment danmuFragment;
129 | TabLayout tabLayout;
130 | ViewPager viewPager;
131 |
132 | boolean isDoubleClick = false;
133 | boolean isFullScreen = false;
134 | boolean seekToLastPlayTime = false;
135 | boolean isSeeking = false;
136 | boolean isChangingVolume = false;
137 | boolean isChangingBrightness = false;
138 | boolean pauseOnStop = false;
139 | int maxVolume,curVolume;
140 | float curBrightness=0.f;
141 | float downX=0.f,downY=0.f;
142 | int downTime=0;
143 | boolean isBlockTop = false, isBlockBottom = false, isBlockRoll = false;
144 | float textScale=1.f;
145 | int snippetStartPos = -1;
146 |
147 |
148 | Timer refreshTimer;
149 | long lastTime = 0L;
150 | OrientationEventListener mOrientationListener;
151 | OrientationHandler orientationHandler;
152 | int startRotation;
153 | int videoWidth, videoHeight;
154 | BaseDanmakuParser parser;
155 | SeekBar.OnSeekBarChangeListener danmuSettingSeekListener;
156 | SharedPreferences.Editor configEditor;
157 |
158 | @Override
159 | protected void onCreate(Bundle savedInstanceState) {
160 | super.onCreate(savedInstanceState);
161 | setContentView(R.layout.activity_play);
162 | SharedPreferences config = getSharedPreferences("config", MODE_PRIVATE);
163 | kikoPlayServer = config.getString("ServerAddress", "");
164 | curAdapter = new PlayListAdapter(this);
165 | if (curList == null) curList = ((KikoPlayApp) getApplication()).curPlayList;
166 | if (selectedItem == null) selectedItem = ((KikoPlayApp) getApplication()).curPlayItem;
167 | curAdapter.setPlayList(curList);
168 | configEditor = getSharedPreferences("config", MODE_PRIVATE).edit();
169 | initViews();
170 | initDanmu();
171 | initConfig(config);
172 | if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
173 | isFullScreen=true;
174 | fullScreen.setImageResource(R.drawable.ic_fullscreen_exit);
175 | }
176 | try {
177 | curBrightness = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS)/255.f;
178 | } catch (Settings.SettingNotFoundException e) {
179 | e.printStackTrace();
180 | }
181 | playlistFragment=PlaylistFragment.newInstance();
182 | danmuFragment=DanmuFragment.newInstance();
183 | playlistFragment.setAdapter(curAdapter);
184 | danmuFragment.setAdapter(pool);
185 | viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
186 | @Override
187 | public Fragment getItem(int i) {
188 | return i==0?playlistFragment:danmuFragment;
189 | }
190 |
191 | @Override
192 | public int getCount() {
193 | return 2;
194 | }
195 |
196 | @Nullable
197 | @Override
198 | public CharSequence getPageTitle(int position) {
199 | return position==0?"列表":"弹幕";
200 | }
201 | });
202 | tabLayout.setupWithViewPager(viewPager);
203 | play(selectedItem);
204 | }
205 | @Override
206 | public void onConfigurationChanged(Configuration newConfig) {
207 | if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
208 | isFullScreen=true;
209 | View decorView = getWindow().getDecorView();
210 | int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
211 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN;
212 | decorView.setSystemUiVisibility(uiOptions);
213 | danmuSetting.setVisibility(View.VISIBLE);
214 | }
215 | else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
216 | int uiFlags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
217 | getWindow().getDecorView().setSystemUiVisibility(uiFlags);
218 | isFullScreen=false;
219 | danmuSetting.setVisibility(View.GONE);
220 | if(danmuSettingView.getVisibility()!=View.INVISIBLE) {
221 | danmuSettingView.setVisibility(View.INVISIBLE);
222 | }
223 | }
224 | resizeVideo();
225 | fullScreen.setImageResource(isFullScreen? R.drawable.ic_fullscreen_exit : R.drawable.ic_fullscreen);
226 | danmuSettingSeekListener.onProgressChanged(danmuRegionSeek,danmuRegionSeek.getProgress(),false);
227 | super.onConfigurationChanged(newConfig);
228 | }
229 |
230 | @Override
231 | protected void onStart() {
232 | super.onStart();
233 | if(player!=null){
234 | if(pauseOnStop){
235 | player.setPlayWhenReady(true);
236 | }
237 | }
238 | }
239 |
240 | @Override
241 | protected void onStop() {
242 | super.onStop();
243 | if(player!=null){
244 | if(player.getPlayWhenReady()) {
245 | player.setPlayWhenReady(false);
246 | pauseOnStop = true;
247 | }
248 | }
249 | }
250 |
251 | @Override
252 | public void onBackPressed() {
253 | if(Build.VERSION.SDK_INT>=24 && isInMultiWindowMode()){
254 | configEditor.apply();
255 | updatePlayTime(curAdapter.getCurPlayItem());
256 | super.onBackPressed();
257 | return;
258 | }
259 | if(isFullScreen){
260 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
261 | }
262 | else{
263 | configEditor.apply();
264 | updatePlayTime(curAdapter.getCurPlayItem());
265 | super.onBackPressed();
266 | }
267 | }
268 |
269 | @Override
270 | protected void onDestroy() {
271 | super.onDestroy();
272 | if(refreshTimer!=null) refreshTimer.cancel();
273 | if(orientationHandler!=null) orientationHandler.removeCallbacksAndMessages(null);
274 | if (player != null) {
275 | player.stop();
276 | player.release();
277 | player = null;
278 | }
279 | if(danmuView!=null) danmuView.release();
280 | }
281 |
282 | void initConfig(SharedPreferences config){
283 | danmuSettingSeekListener=new SeekBar.OnSeekBarChangeListener() {
284 | @Override
285 | public void onProgressChanged(SeekBar seekBar, final int progress, boolean fromUser) {
286 | switch(seekBar.getId()){
287 | case R.id.danmu_opac_seek:
288 | danmuOpacityVal.setText(String.format("%d%%",progress));
289 | danmuContext.setDanmakuTransparency(progress/100.f);
290 | if(fromUser) configEditor.putInt("DanmuOpacity",progress);
291 | break;
292 | case R.id.danmu_speed_seek:
293 | danmuSpeedVal.setText(String.format("%d",progress));
294 | danmuContext.setScrollSpeedFactor((20-progress)/10.f);
295 | if(fromUser) configEditor.putInt("DanmuSpeed",progress);
296 | break;
297 | case R.id.danmu_size_seek:
298 | danmuSizeVal.setText(String.format("%d%%",progress+50));
299 | textScale=(progress+50)/100.f;
300 | danmuContext.setScaleTextSize(textScale);
301 | if(fromUser) configEditor.putInt("DanmuSize",progress);
302 | break;
303 | case R.id.danmu_region_seek:
304 | danmuRegionVal.setText(getResources().getStringArray(R.array.danmu_region)[progress]);
305 | surfaceFrame.post(new Runnable() {
306 | @Override
307 | public void run() {
308 | int maxLens = (int)(surfaceFrame.getHeight()/(25 * textScale * (getResources().getDisplayMetrics().density - 0.5f)));
309 | final int lens=(int)(maxLens*(progress+1)*0.25f);
310 | if(progress==3 || lens<=0) danmuContext.setMaximumLines(null);
311 | else danmuContext.setMaximumLines(new HashMap() {
312 | {
313 | put(BaseDanmaku.TYPE_SCROLL_RL,lens);
314 | put(BaseDanmaku.TYPE_FIX_BOTTOM,lens);
315 | put(BaseDanmaku.TYPE_FIX_TOP,lens);
316 | }
317 | });
318 | }
319 | });
320 | if(fromUser) configEditor.putInt("DanmuRegion",progress);
321 | break;
322 | }
323 | }
324 |
325 | @Override
326 | public void onStartTrackingTouch(SeekBar seekBar) {
327 |
328 | }
329 |
330 | @Override
331 | public void onStopTrackingTouch(SeekBar seekBar) {
332 |
333 | }
334 | };
335 |
336 | danmuOpacitySeek.setOnSeekBarChangeListener(danmuSettingSeekListener);
337 | danmuSpeedSeek.setOnSeekBarChangeListener(danmuSettingSeekListener);
338 | danmuSizeSeek.setOnSeekBarChangeListener(danmuSettingSeekListener);
339 | danmuRegionSeek.setOnSeekBarChangeListener(danmuSettingSeekListener);
340 | danmuRegionVal.setText(getResources().getStringArray(R.array.danmu_region)[3]);
341 |
342 | danmuOpacitySeek.setProgress(config.getInt("DanmuOpacity",100));
343 | danmuSpeedSeek.setProgress(config.getInt("DanmuSpeed",5));
344 | danmuSizeSeek.setProgress(config.getInt("DanmuSize",50));
345 | danmuRegionSeek.setProgress(config.getInt("DanmuRegion",3));
346 |
347 | isBlockTop=config.getBoolean("BlockTop",false);
348 | isBlockRoll=config.getBoolean("BlockRoll",false);
349 | isBlockBottom=config.getBoolean("BlockBottom",false);
350 |
351 | danmuContext.setFTDanmakuVisibility(!isBlockTop);
352 | danmuContext.setR2LDanmakuVisibility(!isBlockRoll);
353 | danmuContext.setFBDanmakuVisibility(!isBlockBottom);
354 |
355 | blockTopView.setTextColor(ContextCompat.getColor(this,isBlockTop?R.color.colorAccent:R.color.controlText));
356 | blockRollView.setTextColor(ContextCompat.getColor(this,isBlockRoll?R.color.colorAccent:R.color.controlText));
357 | blockBottomView.setTextColor(ContextCompat.getColor(this,isBlockBottom?R.color.colorAccent:R.color.controlText));
358 |
359 | View.OnClickListener blockBtnListener=new View.OnClickListener() {
360 | @Override
361 | public void onClick(View v) {
362 | switch (v.getId()){
363 | case R.id.danmu_top:
364 | isBlockTop=!isBlockTop;
365 | blockTopView.setTextColor(ContextCompat.getColor(PlayActivity.this,isBlockTop?R.color.colorAccent:R.color.controlText));
366 | danmuContext.setFTDanmakuVisibility(!isBlockTop);
367 | configEditor.putBoolean("BlockTop",isBlockTop);
368 | break;
369 | case R.id.danmu_roll:
370 | isBlockRoll=!isBlockRoll;
371 | blockRollView.setTextColor(ContextCompat.getColor(PlayActivity.this,isBlockRoll?R.color.colorAccent:R.color.controlText));
372 | danmuContext.setR2LDanmakuVisibility(!isBlockRoll);
373 | configEditor.putBoolean("BlockRoll",isBlockRoll);
374 | break;
375 | case R.id.danmu_bottom:
376 | isBlockBottom=!isBlockBottom;
377 | blockBottomView.setTextColor(ContextCompat.getColor(PlayActivity.this,isBlockBottom?R.color.colorAccent:R.color.controlText));
378 | danmuContext.setFBDanmakuVisibility(!isBlockBottom);
379 | configEditor.putBoolean("BlockBottom",isBlockBottom);
380 | break;
381 | }
382 | }
383 | };
384 | blockTopView.setOnClickListener(blockBtnListener);
385 | blockRollView.setOnClickListener(blockBtnListener);
386 | blockBottomView.setOnClickListener(blockBtnListener);
387 |
388 | }
389 |
390 | @SuppressLint("ClickableViewAccessibility")
391 | void initViews() {
392 | player = ExoPlayerFactory.newSimpleInstance(this, trackSelector);
393 | videoSurface = findViewById(R.id.videoSurface);
394 | subtitleView = findViewById(R.id.subtitleView);
395 | player.setVideoSurfaceView(videoSurface);
396 | surfaceFrame = findViewById(R.id.surfaceFrame);
397 | surfaceAspect=findViewById(R.id.surfaceAspectLayout);
398 | surfaceAspect.setAspectRatio(16.f/9.f);
399 | playControl = findViewById(R.id.playControl);
400 | fullScreen = findViewById(R.id.ctr_fullscreen);
401 | danmuVisible = findViewById(R.id.ctr_danmu);
402 | danmuSetting = findViewById(R.id.ctr_danmu_settings);
403 | danmuLaunch = findViewById(R.id.ctr_danmu_launch);
404 | orientationHandler = new OrientationHandler(this);
405 | danmuView = findViewById(R.id.danmuView);
406 | //controlBtnPanel = findViewById(R.id.controlBtnLayout);
407 | timeText = findViewById(R.id.ctr_time);
408 | videoSeekBar = findViewById(R.id.ctr_seek);
409 | playPause = findViewById(R.id.ctr_pause);
410 | seekTipText = findViewById(R.id.seekTipTextView);
411 | back=findViewById(R.id.ctr_back);
412 | captureImage = findViewById(R.id.ctr_capture_image);
413 | captureSnippet = findViewById(R.id.ctr_capture_snippet);
414 | titleText=findViewById(R.id.ctr_title);
415 | tabLayout=findViewById(R.id.tabs);
416 | viewPager=findViewById(R.id.play_viewPager);
417 |
418 |
419 | danmuSettingView =findViewById(R.id.danmuSettingView);
420 | danmuSettingLayout=findViewById(R.id.danmuSettingLayout);
421 | blockTopView=findViewById(R.id.danmu_top);
422 | blockBottomView=findViewById(R.id.danmu_bottom);
423 | blockRollView=findViewById(R.id.danmu_roll);
424 | danmuOpacitySeek=findViewById(R.id.danmu_opac_seek);
425 | danmuSpeedSeek=findViewById(R.id.danmu_speed_seek);
426 | danmuSizeSeek=findViewById(R.id.danmu_size_seek);
427 | danmuRegionSeek=findViewById(R.id.danmu_region_seek);
428 | danmuOpacityVal=findViewById(R.id.danmu_opac_value);
429 | danmuSpeedVal=findViewById(R.id.danmu_speed_value);
430 | danmuSizeVal=findViewById(R.id.danmu_size_value);
431 | danmuRegionVal=findViewById(R.id.danmu_region_value);
432 |
433 | refreshTimer = new Timer();
434 | mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
435 | maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
436 |
437 | setOrientationListener();
438 | fullScreen.setOnClickListener(new View.OnClickListener() {
439 | @Override
440 | public void onClick(View v) {
441 | isFullScreen = !isFullScreen;
442 | if (isFullScreen)setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
443 | else setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
444 | orientationHandler.sendEmptyMessageDelayed(0, 1000);
445 | }
446 | });
447 |
448 | surfaceFrame.setOnTouchListener(new View.OnTouchListener() {
449 | @Override
450 | public boolean onTouch(View v, MotionEvent event) {
451 | switch (event.getAction()) {
452 | case MotionEvent.ACTION_DOWN: {
453 | long curTime = System.currentTimeMillis();
454 | if(curTime - lastTime < 300){
455 | isDoubleClick=true;
456 | player.setPlayWhenReady(!player.getPlayWhenReady());
457 | }
458 | else {
459 | isDoubleClick=false;
460 | }
461 | lastTime = curTime;
462 | downX=event.getX();
463 | downY=event.getY();
464 | downTime=(int)(player.getCurrentPosition()/1000);
465 | curVolume=mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
466 | WindowManager.LayoutParams lp = getWindow().getAttributes();
467 | if(lp.screenBrightness!=-1) curBrightness=lp.screenBrightness;
468 | }
469 | break;
470 | case MotionEvent.ACTION_MOVE: {
471 | float x=event.getX(),y=event.getY();
472 | int surfaceHeight=surfaceFrame.getHeight();
473 | if(y Math.abs(y-downY) && Math.abs(x-downX) >3*seekStep &&
484 | danmuSettingView.getVisibility()!=View.VISIBLE){
485 | seekTipText.setVisibility(View.VISIBLE);
486 | playControl.setVisibility(View.VISIBLE);
487 | isSeeking=true;
488 | }
489 | else if(!isSeeking && !isChangingBrightness && downX>surfaceFrame.getWidth()/3*2 && Math.abs(x-downX) < Math.abs(y-downY) &&
490 | Math.abs(y-downY) >volumeStep && danmuSettingView.getVisibility()!=View.VISIBLE){
491 | seekTipText.setVisibility(View.VISIBLE);
492 | playControl.setVisibility(View.VISIBLE);
493 | isChangingVolume=true;
494 | }
495 | else if(!isSeeking && !isChangingVolume && downXbrightnessStep && danmuSettingView.getVisibility()!=View.VISIBLE){
497 | seekTipText.setVisibility(View.VISIBLE);
498 | playControl.setVisibility(View.VISIBLE);
499 | isChangingBrightness=true;
500 | }
501 | if(isSeeking){
502 | int total_sec = (int) player.getDuration() / 1000;
503 | int min = total_sec / 60;
504 | int sec = total_sec - min * 60;
505 |
506 | int curPos = downTime + (int)((x-downX)/seekStep);
507 | if(curPos<0) curPos=0;
508 | else if(curPos>total_sec) curPos=total_sec;
509 | int cmin = curPos / 60;
510 | int csec = curPos - cmin * 60;
511 | seekTipText.setText(String.format("%02d:%02d/%02d:%02d", cmin, csec, min, sec));
512 | }
513 | else if(isChangingVolume){
514 | int tmpVolume=curVolume+(int)((downY-y)/volumeStep);
515 | seekTipText.setText(String.format("%d/%d", tmpVolume<0?0:(tmpVolume>maxVolume?maxVolume:tmpVolume),maxVolume));
516 | mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, tmpVolume<0?0:(tmpVolume>maxVolume?maxVolume:tmpVolume), 0);
517 | }
518 | else if(isChangingBrightness){
519 | float tmpBrightness=curBrightness+(downY-y)/brightnessStep/16;
520 | if(tmpBrightness<0) tmpBrightness=0;
521 | else if(tmpBrightness>1.f) tmpBrightness=1.f;
522 | seekTipText.setText(String.format("%d/16", (int)(tmpBrightness*16)));
523 | WindowManager.LayoutParams lp = getWindow().getAttributes();
524 | lp.screenBrightness = tmpBrightness;
525 | getWindow().setAttributes(lp);
526 | }
527 | }
528 | break;
529 | case MotionEvent.ACTION_UP: {
530 | if(isSeeking){
531 | long nTime=(downTime + (int)((event.getX()-downX)*100/surfaceFrame.getWidth()))*1000;
532 | player.seekTo(nTime);
533 | danmuView.seekTo(nTime);
534 |
535 | seekTipText.setVisibility(View.INVISIBLE);
536 | playControl.setVisibility(View.INVISIBLE);
537 | }
538 | else if(isChangingVolume){
539 | seekTipText.setVisibility(View.INVISIBLE);
540 | playControl.setVisibility(View.INVISIBLE);
541 | }
542 | else if(isChangingBrightness){
543 | seekTipText.setVisibility(View.INVISIBLE);
544 | playControl.setVisibility(View.INVISIBLE);
545 | }
546 | else {
547 | if (!isDoubleClick && System.currentTimeMillis() - lastTime < 300) {
548 | if(danmuSettingView.getVisibility()==View.VISIBLE){
549 | if(event.getX()< danmuSettingLayout.getX()){
550 | danmuSettingView.setVisibility(View.INVISIBLE);
551 | }
552 | }
553 | else {
554 | playControl.setVisibility(playControl.getVisibility()!=View.VISIBLE ? View.VISIBLE : View.INVISIBLE);
555 | if(isFullScreen && playControl.getVisibility()==View.INVISIBLE){
556 | int uiFlags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
557 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
558 | | View.SYSTEM_UI_FLAG_FULLSCREEN //hide statusBar
559 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; //hide navigationBar
560 | surfaceFrame.setSystemUiVisibility(uiFlags);
561 | }
562 | }
563 | }
564 | }
565 | isSeeking=false;
566 | isChangingVolume=false;
567 | isChangingBrightness=false;
568 | }
569 | break;
570 | }
571 | return true;
572 |
573 | }
574 | });
575 | videoSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
576 | @Override
577 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
578 | if (!fromUser) return;
579 | player.seekTo(progress * 1000);
580 | danmuView.seekTo((long) progress * 1000);
581 | }
582 |
583 | @Override
584 | public void onStartTrackingTouch(SeekBar seekBar) {
585 |
586 | }
587 |
588 | @Override
589 | public void onStopTrackingTouch(SeekBar seekBar) {
590 |
591 | }
592 | });
593 | back.setOnClickListener(new View.OnClickListener() {
594 | @Override
595 | public void onClick(View v) {
596 | onBackPressed();
597 | }
598 | });
599 | captureImage.setOnClickListener(new View.OnClickListener() {
600 | @Override
601 | public void onClick(View v) {
602 | if (selectedItem == null) return;
603 | try {
604 |
605 |
606 | JSONObject obj = new JSONObject();
607 | int curPos = (int)player.getCurrentPosition()/1000;
608 | int cmin = curPos/ 60;
609 | int csec = curPos - cmin * 60;
610 | String info = String.format("%02d:%02d - %s", cmin, csec, selectedItem.getTitle());
611 | obj.put("mediaId", selectedItem.getMedia());
612 | obj.put("animeName", selectedItem.getAnimeName());
613 | obj.put("pos", curPos);
614 | obj.put("info", info);
615 | HttpUtil.postAsync("http://" + kikoPlayServer + "/api/screenshot", obj.toString(), null, 5000, 5000);
616 |
617 | Snackbar.make(findViewById(R.id.playLayout),
618 | String.format(getString(R.string.capture_image_task_sent), String.format("%02d:%02d", cmin, csec)),
619 | Snackbar.LENGTH_LONG).show();
620 | } catch (Exception e)
621 | {
622 | e.printStackTrace();
623 | }
624 |
625 | }
626 | });
627 | captureSnippet.setOnClickListener(new View.OnClickListener() {
628 | @Override
629 | public void onClick(View v) {
630 | if (selectedItem == null) return;
631 | try {
632 | if(snippetStartPos==-1)
633 | {
634 | snippetStartPos = (int)player.getCurrentPosition()/1000;
635 | captureSnippet.setImageResource(R.drawable.ic_capture_stop);
636 | int cmin = snippetStartPos / 60;
637 | int csec = snippetStartPos - cmin * 60;
638 | Snackbar.make(findViewById(R.id.playLayout),
639 | String.format(getString(R.string.capture_snipped_start_tip), String.format("%02d:%02d", cmin, csec)),
640 | Snackbar.LENGTH_LONG).show();
641 | }
642 | else
643 | {
644 | final int curPos = (int)player.getCurrentPosition()/1000;
645 | AlertDialog.Builder inputDialog = new AlertDialog.Builder(PlayActivity.this);
646 | View view = LayoutInflater.from(PlayActivity.this).inflate(R.layout.dialog_snippet, null);
647 | final CheckBox retainAudioCheck = view.findViewById(R.id.snippet_retain_audio_check);
648 | TextView tipView = view.findViewById(R.id.snippet_info);
649 | int start = snippetStartPos, end = curPos;
650 | if(end < start) {
651 | start = curPos; end = snippetStartPos;
652 | }
653 | if(end - start < 1) end = start + 1;
654 | if(end - start > 15) end = start + 15;
655 | int smin = start / 60, emin = end / 60;
656 | int ssec = start - smin * 60, esec = end - emin * 60;
657 | tipView.setText(String.format(getString(R.string.snippet_info),
658 | String.format("%02d:%02d", smin, ssec),
659 | String.format("%02d:%02d", emin, esec),
660 | end - start));
661 | inputDialog.setTitle(R.string.snippet_dialog_title).setView(view);
662 | final int snippetStart = snippetStartPos, snippetDuraition = end - start;
663 | inputDialog.setPositiveButton("OK",
664 | new DialogInterface.OnClickListener() {
665 | @Override
666 | public void onClick(DialogInterface dialog, int which) {
667 | try{
668 | JSONObject obj = new JSONObject();
669 | int cmin = snippetStart/ 60;
670 | int csec = snippetStart - cmin * 60;
671 | String info = String.format("%02d:%02d, %ds - %s", cmin, csec, snippetDuraition, selectedItem.getTitle());
672 | obj.put("mediaId", selectedItem.getMedia());
673 | obj.put("animeName", selectedItem.getAnimeName());
674 | obj.put("pos", snippetStart);
675 | obj.put("duration", snippetDuraition);
676 | obj.put("info", info);
677 | obj.put("retainAudio", retainAudioCheck.isChecked());
678 | HttpUtil.postAsync("http://" + kikoPlayServer + "/api/screenshot", obj.toString(), null, 5000, 5000);
679 | Snackbar.make(findViewById(R.id.playLayout),
680 | getString(R.string.capture_snipped_stop_tip),
681 | Snackbar.LENGTH_LONG).show();
682 | }
683 | catch (Exception e){
684 | e.printStackTrace();
685 | }
686 | }
687 | }).setNegativeButton("Cancel",null).show();
688 | snippetStartPos = -1;
689 | captureSnippet.setImageResource(R.drawable.ic_capture_start);
690 | }
691 |
692 | } catch (Exception e)
693 | {
694 | e.printStackTrace();
695 | }
696 |
697 | }
698 | });
699 | danmuLaunch.setOnClickListener(new View.OnClickListener() {
700 | @Override
701 | public void onClick(View v) {
702 | if (selectedItem == null || !pool.canLaunch()) return;
703 | final int time = (int)player.getCurrentPosition();
704 | int cmin = time/1000/ 60;
705 | int csec = time/1000 - cmin * 60;
706 |
707 | final EditText editText = new EditText(PlayActivity.this);
708 | AlertDialog.Builder inputDialog = new AlertDialog.Builder(PlayActivity.this);
709 | inputDialog.setTitle(String.format(getString(R.string.launch_dialog_title), String.format("%02d:%02d", cmin, csec)))
710 | .setView(editText);
711 | inputDialog.setPositiveButton("Send",
712 | new DialogInterface.OnClickListener() {
713 | @Override
714 | public void onClick(DialogInterface dialog, int which) {
715 | pool.launch(time, editText.getText().toString());
716 | }
717 | }).setNegativeButton("Cancel",null).show();
718 | }
719 | });
720 | playPause.setOnClickListener(new View.OnClickListener() {
721 | @Override
722 | public void onClick(View v) {
723 | player.setPlayWhenReady(!player.getPlayWhenReady());
724 | }
725 | });
726 | danmuVisible.setOnClickListener(new View.OnClickListener() {
727 | @Override
728 | public void onClick(View v) {
729 | if(danmuView.isShown()){
730 | danmuVisible.setImageResource(R.drawable.ic_danmu_hide);
731 | danmuView.hide();
732 | }
733 | else{
734 | danmuVisible.setImageResource(R.drawable.ic_danmu_show);
735 | danmuView.show();
736 | }
737 | }
738 | });
739 | danmuSetting.setOnClickListener(new View.OnClickListener() {
740 | @Override
741 | public void onClick(View v) {
742 | danmuSettingView.setVisibility(View.VISIBLE);
743 | danmuSettingView.setAnimation(AnimationUtils.loadAnimation(PlayActivity.this, R.anim.move_in_right));
744 | playControl.setVisibility(View.INVISIBLE);
745 |
746 | }
747 | });
748 | refreshTimer.schedule(new TimerTask() {
749 | @Override
750 | public void run() {
751 | final int total_sec = (int) player.getDuration() / 1000;
752 | int min = total_sec / 60;
753 | int sec = total_sec - min * 60;
754 |
755 | final int bufPos = (int) player.getBufferedPosition() / 1000;
756 | final int curPos = (int) player.getCurrentPosition() / 1000;
757 | int cmin = curPos / 60;
758 | int csec = curPos - cmin * 60;
759 | final String time = String.format("%02d:%02d/%02d:%02d", cmin, csec, min, sec);
760 | playControl.post(new Runnable() {
761 | @Override
762 | public void run() {
763 | timeText.setText(time);
764 | videoSeekBar.setMax(total_sec);
765 | videoSeekBar.setSecondaryProgress(bufPos);
766 | videoSeekBar.setProgress(curPos);
767 | }
768 | });
769 | }
770 | }, 1000, 1000);
771 | player.addVideoListener(new VideoListener() {
772 | @Override
773 | public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
774 | videoWidth = width;
775 | videoHeight = height;
776 | resizeVideo();
777 | }
778 | });
779 | player.addTextOutput(new TextRenderer.Output() {
780 | @Override
781 | public void onCues(List cues) {
782 | Log.e("cues are ",cues.toString());
783 | if(subtitleView!=null){
784 | subtitleView.onCues(cues);
785 | }
786 | }
787 | });
788 | player.addListener(new Player.EventListener() {
789 | @Override
790 | public void onLoadingChanged(boolean isLoading) {
791 |
792 | }
793 |
794 | @Override
795 | public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
796 | switch (playbackState) {
797 | case Player.STATE_BUFFERING:
798 | danmuView.pause();
799 | break;
800 | case Player.STATE_ENDED:
801 | danmuView.pause();
802 | PlayListItem next=curAdapter.getNextItem();
803 | if(next!=null) play(next);
804 | else updatePlayTime(curAdapter.getCurPlayItem());
805 | break;
806 | case Player.STATE_READY:
807 | videoSurface.setKeepScreenOn(playWhenReady);
808 | if (seekToLastPlayTime) {
809 | player.seekTo(selectedItem.getPlayTime() * 1000);
810 | danmuView.start((long) selectedItem.getPlayTime() * 1000);
811 | seekToLastPlayTime=false;
812 | }
813 | if (playWhenReady) danmuView.resume();
814 | else danmuView.pause();
815 | }
816 | playPause.setImageResource(playWhenReady ? R.drawable.ic_pause : R.drawable.ic_play);
817 | videoSurface.setKeepScreenOn(playWhenReady);
818 | }
819 |
820 | @Override
821 | public void onPlayerError(ExoPlaybackException error) {
822 | Snackbar.make(findViewById(R.id.playLayout), error.getMessage(), Snackbar.LENGTH_LONG).show();
823 | }
824 | });
825 | }
826 |
827 | void setOrientationListener() {
828 | mOrientationListener = new OrientationEventListener(this) {
829 | @Override
830 | public void onOrientationChanged(int rotation) {
831 | if (startRotation == -2) {
832 | startRotation = rotation;
833 | }
834 | int r = Math.abs(startRotation - rotation);
835 | r = r > 180 ? 360 - r : r;
836 | if (r > 30) {
837 | boolean canRotate = false;
838 | try {
839 | canRotate = (Settings.System.getInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION) == 1);
840 | } catch (Exception e) {
841 | e.printStackTrace();
842 | }
843 | if (canRotate)
844 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
845 | else
846 | setRequestedOrientation(isFullScreen ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
847 | this.disable();
848 | }
849 | }
850 | };
851 | orientationHandler = new OrientationHandler(this);
852 | }
853 |
854 | void resizeVideo() {
855 | if (isFullScreen) {
856 | ViewGroup.LayoutParams p = surfaceFrame.getLayoutParams();
857 | p.height = ViewGroup.LayoutParams.MATCH_PARENT;
858 | p.width = ViewGroup.LayoutParams.MATCH_PARENT;
859 | surfaceFrame.setLayoutParams(p);
860 | } else {
861 | DisplayMetrics dm = new DisplayMetrics();
862 | getWindowManager().getDefaultDisplay().getMetrics(dm);
863 | float h = dm.widthPixels * videoHeight / (float) videoWidth;
864 | ViewGroup.LayoutParams p = surfaceFrame.getLayoutParams();
865 | p.width = ViewGroup.LayoutParams.MATCH_PARENT;
866 | p.height = (int) h;
867 | surfaceFrame.setLayoutParams(p);
868 | }
869 | surfaceAspect.setAspectRatio((float)videoWidth/(float)videoHeight);
870 | }
871 |
872 | void play(final PlayListItem item) {
873 | if(item==curAdapter.getCurPlayItem()) return;
874 | updatePlayTime(curAdapter.getCurPlayItem());
875 | pool.clear();
876 | HttpUtil.getAsync("http://" + kikoPlayServer + "/api/subtitle?id=" + item.getMedia(), new ResponseHandler() {
877 | @Override
878 | public void onResponse(byte[] result) {
879 | if (result != null) {
880 | try {
881 | DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(PlayActivity.this, Util.getUserAgent(PlayActivity.this, "Test"));
882 | MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
883 | .createMediaSource(Uri.parse("http://" + kikoPlayServer + "/media/" + item.getMedia()));
884 | JSONObject obj=new JSONObject(new String(result));
885 | String subtitleType=obj.getString("type");
886 | if(subtitleType.isEmpty()) {
887 | player.prepare(videoSource);
888 | }
889 | else
890 | {
891 | Format subtitleFormat = Format.createTextSampleFormat(null,
892 | subtitleType.equals("srt")?MimeTypes.APPLICATION_SUBRIP:MimeTypes.TEXT_SSA,
893 | Format.NO_VALUE,null);
894 | MediaSource subtitleSource = new SingleSampleMediaSource.Factory(dataSourceFactory)
895 | .createMediaSource(Uri.parse("http://" + kikoPlayServer + "/sub/"+subtitleType+"/" + item.getMedia()), subtitleFormat, C.TIME_UNSET);
896 | MergingMediaSource mergedSource = new MergingMediaSource(videoSource, subtitleSource);
897 | player.prepare(mergedSource);
898 | }
899 | } catch (Exception e) {
900 | e.printStackTrace();
901 | }
902 | selectedItem=item;
903 | pool.loadDanmu(item.getPool(), loadDanmuCallBack);
904 | //loadDanmu(item);
905 | seekToLastPlayTime=(item.getPlayTimeState()==1);
906 | player.setPlayWhenReady(true);
907 | titleText.setText(item.getTitle());
908 | curAdapter.setCurPlayItem(item);
909 | //updateDanmuButton.setEnabled(true);
910 | //updateDanmuButton.setText(getText(R.string.danmu_update));
911 | } else {
912 | Snackbar.make(findViewById(R.id.playLayout), getText(R.string.subtitleInfoFailed), Snackbar.LENGTH_LONG).show();
913 | }
914 | }
915 | });
916 | }
917 |
918 | void initDanmu() {
919 | parser = new BaseDanmakuParser() {
920 | @Override
921 | protected IDanmakus parse() {
922 | return new Danmakus();
923 | }
924 | };
925 | danmuView.setCallback(new DrawHandler.Callback() {
926 | @Override
927 | public void prepared() {
928 | danmuView.start();
929 | }
930 |
931 | @Override
932 | public void updateTimer(DanmakuTimer timer) {
933 |
934 | }
935 |
936 | @Override
937 | public void danmakuShown(BaseDanmaku danmaku) {
938 |
939 | }
940 |
941 | @Override
942 | public void drawingFinished() {
943 |
944 | }
945 | });
946 | danmuView.enableDanmakuDrawingCache(true);
947 | danmuContext = DanmakuContext.create();
948 | danmuContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN,3);//描边
949 | danmuContext.setDuplicateMergingEnabled(true);//重复合并
950 | danmuContext.setScrollSpeedFactor(1.4f);//弹幕滚动速度
951 | danmuContext.setDanmakuTransparency(0.6f);
952 | HashMap overlappingEnablePair = new HashMap<>();
953 | overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_LR, true);
954 | overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true);
955 | danmuContext.preventOverlapping(overlappingEnablePair);
956 | danmuView.prepare(parser,danmuContext);
957 | pool=new DanmuPool(player,danmuView,danmuContext,parser,this,kikoPlayServer);
958 | loadDanmuCallBack=new AsyncCallBack() {
959 | @Override
960 | public void onResponse(int state) {
961 | danmuFragment.updateCountInfo();
962 | if(state<0){
963 | Snackbar.make(findViewById(R.id.playLayout), getText(R.string.loadDanmuFaildTip), Snackbar.LENGTH_LONG)
964 | .setAction("Retry", new View.OnClickListener() {
965 | @Override
966 | public void onClick(View v) {
967 | pool.loadDanmu(selectedItem.getPool(), loadDanmuCallBack);
968 | }
969 | }).show();
970 | }
971 | if(pool.canLaunch()){
972 | danmuLaunch.setVisibility(View.VISIBLE);
973 | } else {
974 | danmuLaunch.setVisibility(View.GONE);
975 | }
976 | }
977 | };
978 | }
979 | void updatePlayTime(PlayListItem item){
980 | if(item==null) return;
981 | JSONObject obj=new JSONObject();
982 | try {
983 | obj.put("mediaId", item.getMedia());
984 | int curPos=(int)(player.getCurrentPosition()/1000);
985 | int duration=(int)(player.getDuration()/1000);
986 | obj.put("playTime", curPos);
987 | int state=0;
988 | if(curPos>15 && curPosduration-15 || item.getPlayTimeState()==2) state=2;
990 | obj.put("playTimeState",state);
991 | curAdapter.setCurPlayTime(curPos,state);
992 | HttpUtil.postAsync("http://" + kikoPlayServer + "/api/updateTime",obj.toString(),null,5000,5000);
993 | setResult(1);
994 | }
995 | catch (Exception e){
996 | e.printStackTrace();
997 | }
998 | }
999 |
1000 | @Override
1001 | public void onPlayItemChanged(PlayListItem item) {
1002 | play(item);
1003 | }
1004 |
1005 | @Override
1006 | public void showUpdateStatus(int state) {
1007 | danmuFragment.updateCountInfo();
1008 | if(state<0){
1009 | Snackbar.make(findViewById(R.id.playLayout), getText(R.string.danmuUpdateFailed), Snackbar.LENGTH_LONG).show();
1010 | }
1011 | }
1012 | }
1013 | class OrientationHandler extends Handler {
1014 | // WeakReference to the outer class's instance.
1015 | private WeakReference mOuter;
1016 |
1017 | OrientationHandler(PlayActivity activity) {
1018 | mOuter = new WeakReference<>(activity);
1019 | }
1020 |
1021 | @Override
1022 | public void handleMessage(Message msg) {
1023 | PlayActivity outer = mOuter.get();
1024 | if (outer != null) {
1025 | outer.startRotation = -2;
1026 | outer.mOrientationListener.enable();
1027 | }
1028 | }
1029 | }
--------------------------------------------------------------------------------