├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── mrthumb
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── reone
│ │ └── mrthumb
│ │ ├── Mrthumb.java
│ │ ├── cache
│ │ └── ThumbCache.java
│ │ ├── creator
│ │ ├── DefaultThumbManagerCreator.java
│ │ └── ThumbManagerCreator.java
│ │ ├── listener
│ │ ├── ProcessListener.java
│ │ └── ThumbProvider.java
│ │ ├── manager
│ │ ├── BaseThumbManager.java
│ │ └── DefaultThumbManager.java
│ │ ├── process
│ │ ├── CacheProcess.java
│ │ ├── DispersionProcess.java
│ │ └── OrderCacheProcess.java
│ │ ├── retriever
│ │ ├── IMediaMetadataRetriever.java
│ │ ├── MediaMetadataRetrieverCompat.java
│ │ ├── impl
│ │ │ ├── FFmpegMediaMetadataRetrieverImpl.java
│ │ │ └── MediaMetadataRetrieverImpl.java
│ │ └── transform
│ │ │ ├── BitmapRotateTransform.java
│ │ │ ├── MetadataKey.java
│ │ │ └── MetadataTransform.java
│ │ └── type
│ │ ├── CacheType.java
│ │ └── RetrieverType.java
│ └── res
│ └── values
│ └── strings.xml
├── settings.gradle
└── simple
├── .gitignore
├── build.gradle
├── preview.gif
├── preview2.gif
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
├── ic_launcher-web.png
├── java
└── com
│ └── reone
│ └── simple
│ ├── SimpleActivity.java
│ ├── SimpleActivityDelegate.java
│ ├── customdemo
│ ├── CustomProcess.java
│ └── CustomThumbManager.java
│ ├── player
│ ├── INiceVideoPlayer.java
│ ├── LogUtil.java
│ ├── NiceTextureView.java
│ ├── NiceUtil.java
│ ├── NiceVideoPlayer.java
│ ├── NiceVideoPlayerController.java
│ ├── PlayerState.java
│ └── ReleasePlayerTask.java
│ └── view
│ ├── BaseCustomize.java
│ ├── BaseCustomizeFrame.java
│ ├── FiexedLayout.java
│ ├── MrthumbAdapter.java
│ ├── ProgressData.java
│ ├── ProgressView.java
│ └── VideoSeekBar.java
└── res
├── anim
└── rotating_anim_loading.xml
├── drawable
├── bottom_cover.png
├── icon_loading.png
├── icon_pause.png
├── icon_play.png
├── icon_zoom_in.png
├── icon_zoom_out.png
├── pd_video_seekbar.xml
└── thumb_seekbar.xml
├── layout
├── activity_simple.xml
├── layout_progress_item.xml
├── layout_recycle_view.xml
└── layout_video_seekbar.xml
├── mipmap-anydpi-v26
├── ic_launcher.xml
└── ic_launcher_round.xml
├── mipmap-hdpi
├── ic_launcher.png
├── ic_launcher_foreground.png
└── ic_launcher_round.png
├── mipmap-mdpi
├── ic_launcher.png
├── ic_launcher_foreground.png
└── ic_launcher_round.png
├── mipmap-xhdpi
├── demo.jpg
├── ic_launcher.png
├── ic_launcher_foreground.png
└── ic_launcher_round.png
├── mipmap-xxhdpi
├── ic_launcher.png
├── ic_launcher_foreground.png
└── ic_launcher_round.png
├── mipmap-xxxhdpi
├── ic_launcher.png
├── ic_launcher_foreground.png
└── ic_launcher_round.png
└── values
├── attrs.xml
├── colors.xml
├── ic_launcher_background.xml
├── strings.xml
└── styles.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | /local.properties
5 | .idea/
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | gradle.properties
11 | gradlew
12 | gradlew.bat
13 | /.idea/vcs.xml
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 拇指先生 Mrthumb
2 | [](https://jitpack.io/#Reone/Mrthumb)
3 |
4 | ## 预览图
5 |
6 |

7 |

8 |
9 |
10 | ## 库说明
11 | - a simple easy video thumbnail provider
12 | - 顺滑的获取视频缩略图
13 | - 支持网络视频缩略图
14 | - 使用方便
15 |
16 | ## 源码下载,分支说明
17 | 请下载对应版本号的分支下载源码浏览,master分支为代码最新状态,却不一定是生成库的源代码,而且有可能是有问题的代码。而生成库后的源码,我会新建一个对应版本号的分支,以保存库源码初始状态。一供查错,二供浏览。
18 |
19 | ## 原理说明
20 | - 使用MediaMetadataRetriever获取视频信息及缩略图
21 | - 使用[MediaMetadataRetrieverCompat](https://github.com/dengyuhan/MediaMetadataRetrieverCompat)支持FFmpeg和自带两种解码方式
22 | - 使用线程异步加载缩略图并缓存
23 | - 支持两种不同的加载顺序选择:顺序、乱序
24 | - 获取不到缩略图时取最近的缩略图
25 |
26 | ## 引用说明
27 | ```groovy
28 | allprojects {
29 | repositories {
30 | ...
31 | maven { url 'https://jitpack.io' }
32 | }
33 | }
34 | ```
35 | ```groovy
36 | dependencies {
37 | implementation 'com.github.Reone:Mrthumb:v1.1.0'
38 | }
39 | ```
40 |
41 | ## 使用说明
42 |
43 | ### 1.加载缩略图
44 | ```java
45 | if (playState == NiceVideoPlayer.STATE_PREPARED) {
46 | //视频准备好后开始加载缩略图
47 | Mrthumb.obtain().buffer(videoUrl, videoDuration, Mrthumb.Default.COUNT);
48 | //更详细的可以调用如下方法
49 | //Mrthumb.obtain().buffer(videoUrl, null, videoDuration, Mrthumb.Default.RETRIEVER_TYPE, Mrthumb.Default.COUNT, Mrthumb.Default.THUMBNAIL_WIDTH, Mrthumb.Default.THUMBNAIL_HEIGHT);
50 | }
51 | ```
52 |
53 | ### 2.获取缩略图
54 | ```java
55 | float percentage = (float) seekBar.getProgress() / seekBar.getMax();
56 | Bitmap bitmap = Mrthumb.obtain().getThumbnail(percentage);
57 | ```
58 |
59 | ### 3.添加缓存进度回调
60 | ```java
61 | Mrthumb.obtain().addProcessListener(new ProcessListener() {
62 |
63 | @Override
64 | public void onProcess(final int index, final int cacheCount, final int maxCount, final long time, final long duration) {
65 | if (delegate != null) {
66 | delegate.thumbProcessLog("cache " + time / 1000 + "s at " + index + " process:" + (cacheCount * 100 / maxCount) + "%");
67 | }
68 | }
69 | });
70 | ```
71 |
72 | ### 4.回收资源
73 | ```java
74 | Mrthumb.obtain().release();
75 | ```
76 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.1.4'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | maven { url 'https://jitpack.io' }
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Sep 25 16:51:22 CST 2018
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-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/mrthumb/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/mrthumb/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 27
5 | defaultConfig {
6 | minSdkVersion 21
7 | targetSdkVersion 27
8 | }
9 |
10 | buildTypes {
11 | release {
12 | minifyEnabled false
13 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
14 | }
15 | }
16 |
17 | }
18 |
19 | dependencies {
20 | implementation 'com.android.support:appcompat-v7:27.1.1'
21 | implementation 'com.github.wseemann:FFmpegMediaMetadataRetriever:1.0.14'
22 | }
23 |
--------------------------------------------------------------------------------
/mrthumb/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 |
--------------------------------------------------------------------------------
/mrthumb/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/Mrthumb.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | import com.reone.mrthumb.creator.DefaultThumbManagerCreator;
6 | import com.reone.mrthumb.creator.ThumbManagerCreator;
7 | import com.reone.mrthumb.listener.ProcessListener;
8 | import com.reone.mrthumb.manager.BaseThumbManager;
9 | import com.reone.mrthumb.type.RetrieverType;
10 |
11 | import java.util.ArrayList;
12 | import java.util.Map;
13 |
14 | /**
15 | * Created by wangxingsheng on 2018/9/27.
16 | * 拇指先生
17 | * 原理: 预先缓存缩略图和下标,需要时读取
18 | */
19 | public class Mrthumb {
20 | private static Mrthumb mInstance = null;
21 | private ArrayList listenerList = new ArrayList<>();
22 | private BaseThumbManager thumbManager;
23 | private boolean dispersionBuffer = true;
24 | private boolean enable = true;
25 | private ThumbManagerCreator thumbManagerCreator;
26 |
27 | public static Mrthumb obtain() {
28 | if (mInstance == null) {
29 | synchronized (Mrthumb.class) {
30 | if (mInstance == null) {
31 | mInstance = new Mrthumb();
32 | }
33 | }
34 | }
35 | return mInstance;
36 | }
37 |
38 | public void buffer(String url, long videoDuration) {
39 | this.buffer(url, null, videoDuration, Default.RETRIEVER_TYPE, Default.COUNT, Default.THUMBNAIL_WIDTH, Default.THUMBNAIL_HEIGHT);
40 | }
41 |
42 | public void buffer(String url, long videoDuration, int count) {
43 | this.buffer(url, null, videoDuration, Default.RETRIEVER_TYPE, count, Default.THUMBNAIL_WIDTH, Default.THUMBNAIL_HEIGHT);
44 | }
45 |
46 | public void buffer(String url, Map headers, long videoDuration) {
47 | this.buffer(url, headers, videoDuration, Default.RETRIEVER_TYPE, Default.COUNT, Default.THUMBNAIL_WIDTH, Default.THUMBNAIL_HEIGHT);
48 | }
49 |
50 | public void buffer(String url, Map headers, long videoDuration, int count) {
51 | this.buffer(url, headers, videoDuration, Default.RETRIEVER_TYPE, count, Default.THUMBNAIL_WIDTH, Default.THUMBNAIL_HEIGHT);
52 | }
53 |
54 | public Mrthumb manager(ThumbManagerCreator thumbManagerCreator) {
55 | this.thumbManagerCreator = thumbManagerCreator;
56 | return this;
57 | }
58 |
59 | /**
60 | * @param url 视频链接
61 | * @param headers 指定头
62 | * @param videoDuration 视频时长
63 | * @param retrieverType 解码器类型
64 | * @param thumbnailWidth 生成缩略图宽度
65 | * @param thumbnailHeight 生成缩略图高度
66 | */
67 | public void buffer(String url, Map headers, long videoDuration, @RetrieverType int retrieverType, int count, int thumbnailWidth, int thumbnailHeight) {
68 | if (thumbManager == null) {
69 | thumbManager = getThumbManagerCreator().createThumbManager(count, listenerList);
70 | }
71 | thumbManager.onBufferStart(url, headers, videoDuration, retrieverType, count, thumbnailWidth, thumbnailHeight);
72 | }
73 |
74 | private ThumbManagerCreator getThumbManagerCreator() {
75 | if (thumbManagerCreator == null) {
76 | thumbManagerCreator = new DefaultThumbManagerCreator();
77 | }
78 | return thumbManagerCreator;
79 | }
80 |
81 | /**
82 | * 通过百分比获取缩略图
83 | *
84 | * @param percentage 百分比
85 | * @return 缩略图
86 | */
87 | public Bitmap getThumbnail(float percentage) {
88 | if (thumbManager != null) {
89 | return thumbManager.getThumbnail(percentage);
90 | }
91 | return null;
92 | }
93 |
94 | public void release() {
95 | if (thumbManager != null) {
96 | thumbManager.release();
97 | }
98 | listenerList.clear();
99 | }
100 |
101 | public void addProcessListener(ProcessListener processListener) {
102 | listenerList.add(processListener);
103 | }
104 |
105 | public boolean isDispersionBuffer() {
106 | return dispersionBuffer;
107 | }
108 |
109 | public Mrthumb dispersion(boolean dispersionBuffer) {
110 | this.dispersionBuffer = dispersionBuffer;
111 | return this;
112 | }
113 |
114 | public boolean isEnable() {
115 | return enable;
116 | }
117 |
118 | public void setEnable(boolean enable) {
119 | this.enable = enable;
120 | }
121 |
122 | public static class Default {
123 | public static final int COUNT = 100;
124 | public static final int RETRIEVER_TYPE = RetrieverType.RETRIEVER_ANDROID;
125 | public static final int THUMBNAIL_WIDTH = 320;
126 | public static final int THUMBNAIL_HEIGHT = 180;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/cache/ThumbCache.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.cache;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | import java.util.Arrays;
6 |
7 | /**
8 | * Created by wangxingsheng on 2018/9/30.
9 | */
10 | public class ThumbCache {
11 | private int max = 0;
12 | private int lastIndex = 0;//最后一个存在的位置
13 | private Bitmap[] thumbnails;
14 |
15 | private ThumbCache() {
16 | thumbnails = new Bitmap[max];
17 | }
18 |
19 | private static ThumbCache mInstance = null;
20 |
21 | public static ThumbCache getInstance() {
22 | if (mInstance == null) {
23 | synchronized (ThumbCache.class) {
24 | if (mInstance == null) {
25 | mInstance = new ThumbCache();
26 | }
27 | }
28 | }
29 | return mInstance;
30 | }
31 |
32 | public Bitmap get(int index) {
33 | if (index < 0 || index >= max) {
34 | return thumbnails[lastIndex];
35 | }
36 | return thumbnails[index];
37 | }
38 |
39 | public boolean hasThumbnail(int index) {
40 | return index > 0 && index < max && thumbnails[index] != null;
41 | }
42 |
43 | public void set(int index, Bitmap thumbnail) {
44 | if (index < 0 || index >= max) {
45 | return;
46 | }
47 | lastIndex = Math.max(index, lastIndex);
48 | this.thumbnails[index] = thumbnail;
49 | }
50 |
51 | public void setCacheMax(int maxSize) {
52 | max = Math.max(max, maxSize);
53 | if (thumbnails == null) {
54 | thumbnails = new Bitmap[max];
55 | } else {
56 | thumbnails = Arrays.copyOf(thumbnails, max);
57 | }
58 | }
59 |
60 | public void release() {
61 | if (thumbnails != null) {
62 | try {
63 | for (Bitmap bitmap : thumbnails) {
64 | if (bitmap != null) {
65 | bitmap.recycle();
66 | }
67 | }
68 | } catch (Exception e) {
69 | e.printStackTrace();
70 | }
71 | thumbnails = null;
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/creator/DefaultThumbManagerCreator.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.creator;
2 |
3 | import com.reone.mrthumb.listener.ProcessListener;
4 | import com.reone.mrthumb.manager.BaseThumbManager;
5 | import com.reone.mrthumb.manager.DefaultThumbManager;
6 |
7 | import java.util.ArrayList;
8 |
9 | /**
10 | * Created by wangxingsheng on 2020/6/15.
11 | * desc:
12 | */
13 | public class DefaultThumbManagerCreator implements ThumbManagerCreator {
14 |
15 | @Override
16 | public BaseThumbManager createThumbManager(int count, final ArrayList processListeners) {
17 | DefaultThumbManager temp = new DefaultThumbManager(count);
18 | temp.setProcessListener(new ProcessListener() {
19 | @Override
20 | public void onProcess(int index, int cacheCount, int maxCount, long time, long duration) {
21 | for (ProcessListener listener : processListeners) {
22 | listener.onProcess(index, cacheCount, maxCount, time, duration);
23 | }
24 | }
25 | });
26 | return temp;
27 | }
28 | }
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/creator/ThumbManagerCreator.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.creator;
2 |
3 | import com.reone.mrthumb.listener.ProcessListener;
4 | import com.reone.mrthumb.manager.BaseThumbManager;
5 |
6 | import java.util.ArrayList;
7 |
8 | /**
9 | * Created by wangxingsheng on 2020/6/15.
10 | * desc:
11 | */
12 | public interface ThumbManagerCreator {
13 | /**
14 | * 获取ThumbManager
15 | *
16 | * @param processListeners 添加在Mrthumb上的监听
17 | * @return ThumbManager不能为空
18 | */
19 | BaseThumbManager createThumbManager(int count, ArrayList processListeners);
20 | }
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/listener/ProcessListener.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.listener;
2 |
3 | /**
4 | * Created by wangxingsheng on 2018/9/30.
5 | * desc:缩略图加载进度回调
6 | */
7 | public interface ProcessListener {
8 |
9 | /**
10 | * 缩略图加载进度回调
11 | *
12 | * @param index 缩略图加载位置
13 | * @param cacheCount 已缓存数量
14 | * @param maxCount 需要缓存总数
15 | * @param time 缓存缩略图所在秒数
16 | * @param duration 视频总时长
17 | */
18 | void onProcess(int index, int cacheCount, int maxCount, long time, long duration);
19 | }
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/listener/ThumbProvider.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.listener;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | /**
6 | * Created by wangxingsheng on 2018/10/23.
7 | * desc:提供缩略图,在缩略图缓存数组填充的时候,Process需要通过这个类来获取对应的缓存数据
8 | */
9 | public interface ThumbProvider {
10 |
11 | /**
12 | * 获取缩略图
13 | *
14 | * @param i 需要获取的下标
15 | * @return 提供缩略图的bitmap
16 | */
17 | Bitmap getIndex(int i);
18 |
19 | /**
20 | * @return 一共需要缓存多少张缩略图,一般根据进度条选择100
21 | */
22 | int maxSize();
23 | }
24 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/manager/BaseThumbManager.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.manager;
2 |
3 | import android.graphics.Bitmap;
4 | import android.os.SystemClock;
5 | import android.util.Log;
6 |
7 | import com.reone.mrthumb.Mrthumb;
8 | import com.reone.mrthumb.cache.ThumbCache;
9 | import com.reone.mrthumb.listener.ThumbProvider;
10 | import com.reone.mrthumb.process.CacheProcess;
11 | import com.reone.mrthumb.process.DispersionProcess;
12 | import com.reone.mrthumb.process.OrderCacheProcess;
13 | import com.reone.mrthumb.type.RetrieverType;
14 | import com.reone.tbufferlib.BuildConfig;
15 |
16 | import java.util.Map;
17 |
18 | /**
19 | * Created by wangxingsheng on 2018/5/19.
20 | * 执行Process
21 | */
22 | public abstract class BaseThumbManager {
23 | protected int maxSize;
24 | private CacheProcess process;
25 | private Thread initThread = new Thread("MrthumbCacheThread") {
26 | @Override
27 | public void run() {
28 | long startBufferTime = SystemClock.elapsedRealtime();
29 | onThreadStart();
30 | try {
31 | if (Mrthumb.obtain().isEnable()) {
32 | process = getCacheProcess();
33 | process.start();
34 | }
35 | } catch (Exception e) {
36 | e.printStackTrace();
37 | }
38 | log("ThumbnailBuffer end buffer at " + (SystemClock.elapsedRealtime() - startBufferTime) + "/n");
39 | }
40 | };
41 |
42 | public BaseThumbManager(int maxSize) {
43 | this.maxSize = maxSize;
44 | ThumbCache.getInstance().setCacheMax(maxSize);
45 | }
46 |
47 | protected void execute() {
48 | if (!initThread.isInterrupted()) {
49 | initThread.interrupt();
50 | }
51 | initThread.start();
52 | }
53 |
54 | /**
55 | * 通过百分比获取缩略图
56 | *
57 | * @param percentage 选择时间点占总时长的百分比
58 | * @return 缩略图
59 | */
60 | public Bitmap getThumbnail(float percentage) {
61 | int index = (int) ((maxSize - 1) * percentage);
62 | if (process == null) return null;
63 | Bitmap bitmap = process.get(index);
64 | logBitmapSize(bitmap);
65 | return bitmap;
66 | }
67 |
68 | /**
69 | * MrthumbCacheThread 启动回调
70 | */
71 | protected void onThreadStart() {
72 |
73 | }
74 |
75 | /**
76 | * 获取缓存过程,如果返回为空,使用默认
77 | */
78 | public CacheProcess getCacheProcess() {
79 | if (Mrthumb.obtain().isDispersionBuffer()) {
80 | return new DispersionProcess(getThumbProvider());
81 | } else {
82 | return new OrderCacheProcess(getThumbProvider());
83 | }
84 | }
85 |
86 | protected abstract ThumbProvider getThumbProvider();
87 |
88 | private void log(String log) {
89 | if (BuildConfig.DEBUG) {
90 | Log.d(BaseThumbManager.class.getSimpleName(), log);
91 | }
92 | }
93 |
94 | public void release() {
95 | if (initThread != null) {
96 | initThread.interrupt();
97 | }
98 | if (process != null) {
99 | process.release();
100 | }
101 | }
102 |
103 | private void logBitmapSize(Bitmap bitmap) {
104 | if (bitmap == null) return;
105 | log("ThumbnailBuffer bitmap size " + bitmap.getByteCount());
106 | }
107 |
108 | /**
109 | * 开始缓存回调,需要调用{@linkplain BaseThumbManager#execute()}让缓存线程启动
110 | *
111 | * @param url 视频链接
112 | * @param headers 指定头
113 | * @param videoDuration 视频时长
114 | * @param retrieverType 解码器类型
115 | * @param thumbnailWidth 生成缩略图宽度
116 | * @param thumbnailHeight 生成缩略图高度
117 | */
118 | public void onBufferStart(String url, Map headers, long videoDuration, @RetrieverType int retrieverType, int count, int thumbnailWidth, int thumbnailHeight){
119 | execute();
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/manager/DefaultThumbManager.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.manager;
2 |
3 | import android.graphics.Bitmap;
4 | import android.util.Log;
5 |
6 | import com.reone.mrthumb.listener.ProcessListener;
7 | import com.reone.mrthumb.listener.ThumbProvider;
8 | import com.reone.mrthumb.retriever.MediaMetadataRetrieverCompat;
9 | import com.reone.mrthumb.type.RetrieverType;
10 | import com.reone.tbufferlib.BuildConfig;
11 |
12 | import java.util.HashMap;
13 | import java.util.Map;
14 |
15 | /**
16 | * Created by wangxingsheng on 2019-08-30.
17 | * desc:执行Process
18 | */
19 | public class DefaultThumbManager extends BaseThumbManager {
20 |
21 | private MediaMetadataRetrieverCompat mmr;
22 | private int cacheCount;
23 | private int thumbnailWidth;
24 | private int thumbnailHeight;
25 | private long duration;
26 | private String mUrl;
27 | private Map mHeaders;
28 | private ProcessListener processListener;
29 |
30 | public DefaultThumbManager(int maxSize) {
31 | super(maxSize);
32 | }
33 |
34 | @Override
35 | public void onBufferStart(String url, Map headers, long videoDuration, @RetrieverType int retrieverType, int count, int thumbnailWidth, int thumbnailHeight) {
36 | this.mmr = new MediaMetadataRetrieverCompat(retrieverType);
37 | this.duration = videoDuration;
38 | log("ThumbnailBuffer mmr = " + mmr + " duration = " + duration);
39 | log("ThumbnailBuffer url = " + url);
40 | log("ThumbnailBuffer headers = " + headers);
41 | this.thumbnailWidth = thumbnailWidth;
42 | this.thumbnailHeight = thumbnailHeight;
43 | if (url == null || mmr == null) {
44 | throw new RuntimeException("url or mmr is null");
45 | }
46 | if (url.equals(mUrl)) return;
47 | release();
48 | mUrl = url;
49 | mHeaders = headers;
50 | cacheCount = 0;
51 | super.execute();
52 | }
53 |
54 | @Override
55 | protected void onThreadStart() {
56 | if (mHeaders == null) {
57 | mmr.setDataSource(mUrl, new HashMap());
58 | } else {
59 | mmr.setDataSource(mUrl, mHeaders);
60 | }
61 | mmr.extractMetadata(MediaMetadataRetrieverCompat.METADATA_KEY_DURATION);
62 | }
63 |
64 | @Override
65 | protected ThumbProvider getThumbProvider() {
66 | return new ThumbProvider() {
67 | @Override
68 | public Bitmap getIndex(int index) {
69 | Bitmap bitmap = null;
70 | try {
71 | long time = index * duration / maxSize;
72 | log("ThumbnailBuffer dispersions record buffer i = " + index + " at time:" + time);
73 | bitmap = mmr.getScaledFrameAtTime(time * 1000, MediaMetadataRetrieverCompat.OPTION_CLOSEST,
74 | thumbnailWidth, thumbnailHeight);
75 | if (bitmap == null) {
76 | log("ThumbnailBuffer dispersions record buffer i = " + index + " is null");
77 | }
78 | if (processListener != null) {
79 | processListener.onProcess(index, ++cacheCount, maxSize, time, duration);
80 | }
81 | } catch (Exception ignore) {
82 | }
83 | return bitmap;
84 | }
85 |
86 | @Override
87 | public int maxSize() {
88 | return maxSize;
89 | }
90 | };
91 | }
92 |
93 | @Override
94 | public void release() {
95 | super.release();
96 | mUrl = null;
97 | mHeaders = null;
98 | }
99 |
100 | public void setProcessListener(ProcessListener processListener) {
101 | this.processListener = processListener;
102 | }
103 |
104 | private void log(String log) {
105 | if (BuildConfig.DEBUG) {
106 | Log.d(BaseThumbManager.class.getSimpleName(), log);
107 | }
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/process/CacheProcess.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.process;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | import com.reone.mrthumb.listener.ThumbProvider;
6 |
7 | /**
8 | * Created by wangxingsheng on 2018/10/17.
9 | * desc: 缓存过程,不同的实现类,使缓存通过不同的顺序进行缩略图获取,以及缓存中缩略图的使用
10 | */
11 | public abstract class CacheProcess {
12 | int maxSize;
13 | private ThumbProvider thumbProvider;
14 |
15 | public CacheProcess(ThumbProvider thumbProvider) {
16 | this.thumbProvider = thumbProvider;
17 | this.maxSize = thumbProvider.maxSize();
18 | }
19 |
20 | public ThumbProvider getThumbProvider() {
21 | return thumbProvider;
22 | }
23 |
24 | /**
25 | * 开始获取缩略图存入缓存
26 | */
27 | public abstract void start();
28 |
29 | /**
30 | * 从缓存中获取缩略图
31 | *
32 | * @param index 缩略图在数组中的下标
33 | * @return 缩略图
34 | */
35 | public abstract Bitmap get(int index);
36 |
37 | /**
38 | * 回收缩略图缓存
39 | */
40 | public abstract void release();
41 | }
42 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/process/DispersionProcess.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.process;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | import com.reone.mrthumb.cache.ThumbCache;
6 | import com.reone.mrthumb.listener.ThumbProvider;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collections;
10 |
11 | /**
12 | * Created by wangxingsheng on 2018/10/17.
13 | * desc:分散式填充程序
14 | */
15 | public class DispersionProcess extends CacheProcess {
16 | private ArrayList bufferIndex = new ArrayList<>();
17 |
18 | public DispersionProcess(ThumbProvider thumbProvider) {
19 | super(thumbProvider);
20 | }
21 |
22 | @Override
23 | public void start() {
24 | int base = 1;
25 | ThumbCache.getInstance().set(0, getThumbProvider().getIndex(0));
26 | bufferIndex.add(0);
27 | while (base < maxSize) {
28 | int step = ((maxSize - 1) / base);
29 | int i = step;
30 | while (i < maxSize) {
31 | if (!ThumbCache.getInstance().hasThumbnail(i)) {
32 | ThumbCache.getInstance().set(i, getThumbProvider().getIndex(i));
33 | bufferIndex.add(i);
34 | }
35 | i += step;
36 | }
37 | base++;
38 | }
39 | }
40 |
41 | @Override
42 | public Bitmap get(int index) {
43 | if (bufferIndex.contains(index)) {
44 | return ThumbCache.getInstance().get(index);
45 | } else if (index == 0 || index == maxSize - 1) {
46 | return null;
47 | } else {
48 | ArrayList softIndex = new ArrayList<>(bufferIndex);
49 | if (softIndex.size() == 0) {
50 | return null;
51 | }
52 | softIndex.add(index);
53 | Collections.sort(softIndex);
54 | int i = softIndex.indexOf(index);
55 | if (i == 0) {
56 | return ThumbCache.getInstance().get(softIndex.get(i + 1));
57 | } else if (i == softIndex.size() - 1) {
58 | return ThumbCache.getInstance().get(softIndex.get(i - 1));
59 | } else if (Math.abs(softIndex.get(i) - softIndex.get(i - 1))
60 | < Math.abs(softIndex.get(i + 1) - Math.abs(softIndex.get(i)))) {
61 | return ThumbCache.getInstance().get(softIndex.get(i - 1));
62 | } else {
63 | return ThumbCache.getInstance().get(softIndex.get(i + 1));
64 | }
65 | }
66 | }
67 |
68 | @Override
69 | public void release() {
70 | ThumbCache.getInstance().release();
71 | bufferIndex.clear();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/process/OrderCacheProcess.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.process;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | import com.reone.mrthumb.cache.ThumbCache;
6 | import com.reone.mrthumb.listener.ThumbProvider;
7 |
8 | /**
9 | * Created by wangxingsheng on 2018/10/17.
10 | * desc:顺序填充
11 | */
12 | public class OrderCacheProcess extends CacheProcess {
13 |
14 | public OrderCacheProcess(ThumbProvider thumbProvider) {
15 | super(thumbProvider);
16 | }
17 |
18 | @Override
19 | public void start() {
20 | for (int i = 0; i < maxSize; i++) {
21 | if (ThumbCache.getInstance().hasThumbnail(i)) return;
22 | try {
23 | ThumbCache.getInstance().set(i, getThumbProvider().getIndex(i));
24 | } catch (Exception e) {
25 | e.printStackTrace();
26 | }
27 | }
28 | }
29 |
30 | @Override
31 | public Bitmap get(int index) {
32 | return ThumbCache.getInstance().get(index);
33 | }
34 |
35 | @Override
36 | public void release() {
37 | ThumbCache.getInstance().release();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/retriever/IMediaMetadataRetriever.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.retriever;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.net.Uri;
6 |
7 | import java.util.Map;
8 |
9 | /**
10 | * author dengyuhan
11 | * created 2017/5/26 14:49
12 | */
13 | public interface IMediaMetadataRetriever {
14 |
15 | void setDataSource(String path);
16 |
17 | void setDateSource(String uri, Map headers);
18 |
19 | void setDataSource(Context context, Uri uri);
20 |
21 | Bitmap getFrameAtTime();
22 |
23 | Bitmap getFrameAtTime(long timeUs, int option);
24 |
25 | Bitmap getScaledFrameAtTime(long timeUs, int width, int height);
26 |
27 | Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int height);
28 |
29 | byte[] getEmbeddedPicture();
30 |
31 | String extractMetadata(String keyCode);
32 |
33 | void release();
34 | }
35 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/retriever/MediaMetadataRetrieverCompat.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.retriever;
2 |
3 | import android.graphics.Bitmap;
4 | import android.text.TextUtils;
5 | import android.util.Log;
6 |
7 | import com.reone.mrthumb.type.RetrieverType;
8 | import com.reone.mrthumb.retriever.impl.FFmpegMediaMetadataRetrieverImpl;
9 | import com.reone.mrthumb.retriever.impl.MediaMetadataRetrieverImpl;
10 | import com.reone.mrthumb.retriever.transform.BitmapRotateTransform;
11 | import com.reone.mrthumb.retriever.transform.MetadataTransform;
12 |
13 | import java.io.File;
14 | import java.io.FileNotFoundException;
15 | import java.util.Map;
16 |
17 | /**
18 | * author dengyuhan
19 | * created 2017/5/26 14:48
20 | */
21 | public class MediaMetadataRetrieverCompat {
22 | private final String TAG = "MediaMetadataRetriever";
23 |
24 | private IMediaMetadataRetriever impl;
25 | private MediaMetadataRetrieverImpl androidImpl;
26 | private String mPath;
27 |
28 | public static final int OPTION_PREVIOUS_SYNC = 0x00;
29 | public static final int OPTION_NEXT_SYNC = 0x01;
30 | public static final int OPTION_CLOSEST_SYNC = 0x02;
31 | public static final int OPTION_CLOSEST = 0x03;
32 |
33 | public static final int METADATA_KEY_ALBUM = 1;
34 | public static final int METADATA_KEY_ARTIST = 2;
35 | public static final int METADATA_KEY_AUTHOR = 3;
36 | public static final int METADATA_KEY_COMPOSER = 4;
37 | public static final int METADATA_KEY_DATE = 5;
38 | public static final int METADATA_KEY_TITLE = 7;
39 | public static final int METADATA_KEY_DURATION = 9;
40 | public static final int METADATA_KEY_NUM_TRACKS = 10;
41 | public static final int METADATA_KEY_ALBUMARTIST = 13;
42 | public static final int METADATA_KEY_DISC_NUMBER = 14;
43 | public static final int METADATA_KEY_VIDEO_WIDTH = 18;
44 | public static final int METADATA_KEY_VIDEO_HEIGHT = 19;
45 | public static final int METADATA_KEY_VIDEO_ROTATION = 24;
46 | public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25;
47 |
48 | public MediaMetadataRetrieverCompat() {
49 | this(RetrieverType.RETRIEVER_FFMPEG);
50 | }
51 |
52 | public MediaMetadataRetrieverCompat(@RetrieverType int type) {
53 | if (type == RetrieverType.RETRIEVER_FFMPEG) {
54 | try {
55 | //创建实例前先检查是否引入FFmpegMediaMetadataRetriever
56 | Class.forName("wseemann.media.FFmpegMediaMetadataRetriever");
57 | //优先ffmpeg
58 | this.impl = new FFmpegMediaMetadataRetrieverImpl();
59 | } catch (Exception e) {
60 | //不行就自带的
61 | this.impl = new MediaMetadataRetrieverImpl();
62 | Log.d(TAG, "FFmpegMediaMetadataRetrieverImpl初始化失败,使用原生API");
63 | e.printStackTrace();
64 | }
65 | } else {
66 | this.impl = new MediaMetadataRetrieverImpl();
67 | }
68 | }
69 |
70 | public IMediaMetadataRetriever getMediaMetadataRetriever() {
71 | return impl;
72 | }
73 |
74 | /**
75 | * @param mUrl
76 | * @param path
77 | * @deprecated Use {@link #setMediaDataSource(String)} instead.
78 | */
79 | @Deprecated
80 | public void setDataSource(String mUrl, String path) {
81 | try {
82 | setMediaDataSource(path);
83 | } catch (FileNotFoundException e) {
84 | e.printStackTrace();
85 | }
86 | }
87 |
88 | public void setMediaDataSource(String path) throws FileNotFoundException {
89 | setMediaDataSource(new File(path));
90 | }
91 |
92 | public void setDataSource(String uri, Map headers) {
93 | this.impl.setDateSource(uri, headers);
94 | }
95 |
96 | public void setMediaDataSource(File file) throws FileNotFoundException {
97 | if (!file.exists()) {
98 | throw new FileNotFoundException();
99 | }
100 | this.mPath = file.getAbsolutePath();
101 | this.impl.setDataSource(this.mPath);
102 | if (this.androidImpl != null) {
103 | this.androidImpl.setDataSource(this.mPath);
104 | }
105 | }
106 |
107 | public String extractMetadata(int keyCode) {
108 | String keyCodeString = MetadataTransform.transform(this.impl.getClass(), keyCode);
109 | if (TextUtils.isEmpty(keyCodeString)) {
110 | return null;
111 | }
112 | String metadata = this.impl.extractMetadata(keyCodeString);
113 | if (metadata == null) {
114 | //如果ffmpeg失败,自带api替代
115 | String androidKeyCodeString = MetadataTransform.transform(MediaMetadataRetrieverImpl.class, keyCode);
116 | metadata = getAndroidMediaMetadataRetriever().extractMetadata(androidKeyCodeString);
117 | }
118 | return metadata;
119 | }
120 |
121 | public Bitmap getFrameAtTime() {
122 | Bitmap frame = this.impl.getFrameAtTime();
123 | if (frame == null) {
124 | return getAndroidMediaMetadataRetriever().getFrameAtTime();
125 | } else {
126 | return frame;
127 | }
128 | }
129 |
130 | public Bitmap getFrameAtTime(long timeUs, int option) {
131 | Bitmap frame = this.impl.getFrameAtTime(timeUs, option);
132 | if (frame == null) {
133 | return getAndroidMediaMetadataRetriever().getFrameAtTime(timeUs, option);
134 | } else {
135 | return frame;
136 | }
137 | }
138 |
139 | public Bitmap getScaledFrameAtTime(long timeUs, int width, int height) {
140 | Bitmap frame = this.impl.getScaledFrameAtTime(timeUs, width, height);
141 | if (frame == null) {
142 | return getAndroidMediaMetadataRetriever().getScaledFrameAtTime(timeUs, width, height);
143 | } else {
144 | return frame;
145 | }
146 | }
147 |
148 | public Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int height) {
149 | Bitmap frame = this.impl.getScaledFrameAtTime(timeUs, option, width, height);
150 | if (frame == null) {
151 | return getAndroidMediaMetadataRetriever().getScaledFrameAtTime(timeUs, option, width, height);
152 | } else {
153 | return frame;
154 | }
155 | }
156 |
157 | public Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int height, float rotate) {
158 | boolean isVertical = isVertical(rotate);
159 | Bitmap frame = getScaledFrameAtTime(timeUs, option,
160 | isVertical ? height : width, isVertical ? width : height);
161 | if (isRotate(rotate)) {
162 | return BitmapRotateTransform.transform(frame, rotate);
163 | }
164 | return frame;
165 | }
166 |
167 | public Bitmap getScaledFrameAtTime(long timeUs, int option, float widthScale, float heightScale, float rotate) {
168 | String widthText = extractMetadata(METADATA_KEY_VIDEO_WIDTH);
169 | String heightText = extractMetadata(METADATA_KEY_VIDEO_HEIGHT);
170 | int width = TextUtils.isEmpty(widthText) ? 0 : (int) (Integer.parseInt(widthText) * widthScale);
171 | int height = TextUtils.isEmpty(heightText) ? 0 : (int) (Integer.parseInt(heightText) * heightScale);
172 | return getScaledFrameAtTime(timeUs, option, width, height, rotate);
173 | }
174 |
175 | public byte[] getEmbeddedPicture() {
176 | return impl.getEmbeddedPicture();
177 | }
178 |
179 |
180 | public void release() {
181 | impl.release();
182 | }
183 |
184 |
185 | private MediaMetadataRetrieverImpl getAndroidMediaMetadataRetriever() {
186 | //如果用的是自带的
187 | if (impl instanceof MediaMetadataRetrieverImpl) {
188 | return (MediaMetadataRetrieverImpl) impl;
189 | }
190 | if (androidImpl == null) {
191 | this.androidImpl = new MediaMetadataRetrieverImpl();
192 | if (!TextUtils.isEmpty(this.mPath)) {
193 | this.androidImpl.setDataSource(this.mPath);
194 | }
195 | }
196 | return this.androidImpl;
197 | }
198 |
199 |
200 | private boolean isVertical(float rotate) {
201 | float abs = Math.abs(rotate);
202 | return abs == 90 || abs == 270;
203 | }
204 |
205 |
206 | /**
207 | * 是否有角度
208 | *
209 | * @param rotate
210 | * @return
211 | */
212 | public static boolean isRotate(float rotate) {
213 | float abs = Math.abs(rotate);
214 | return abs % 360 != 0;
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/retriever/impl/FFmpegMediaMetadataRetrieverImpl.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.retriever.impl;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.net.Uri;
6 |
7 | import com.reone.mrthumb.retriever.IMediaMetadataRetriever;
8 |
9 | import java.util.Map;
10 |
11 | import wseemann.media.FFmpegMediaMetadataRetriever;
12 |
13 | /**
14 | * 基于ffmpeg实现
15 | * author dengyuhan
16 | * created 2017/5/26 14:51
17 | */
18 | public class FFmpegMediaMetadataRetrieverImpl implements IMediaMetadataRetriever {
19 | private FFmpegMediaMetadataRetriever mRetriever;
20 |
21 | public FFmpegMediaMetadataRetrieverImpl() {
22 | this.mRetriever = new FFmpegMediaMetadataRetriever();
23 | }
24 |
25 | @Override
26 | public void setDataSource(String path) {
27 | this.mRetriever.setDataSource(path);
28 | }
29 |
30 | @Override
31 | public void setDateSource(String uri, Map headers) {
32 | this.mRetriever.setDataSource(uri, headers);
33 | }
34 |
35 | @Override
36 | public void setDataSource(Context context, Uri uri) {
37 | this.mRetriever.setDataSource(context, uri);
38 | }
39 |
40 | @Override
41 | public Bitmap getFrameAtTime() {
42 | return this.mRetriever.getFrameAtTime();
43 | }
44 |
45 | @Override
46 | public Bitmap getFrameAtTime(long timeUs, int option) {
47 | return this.mRetriever.getFrameAtTime(timeUs, option);
48 | }
49 |
50 | @Override
51 | public Bitmap getScaledFrameAtTime(long timeUs, int width, int height) {
52 | return this.mRetriever.getScaledFrameAtTime(timeUs, width, height);
53 | }
54 |
55 | @Override
56 | public Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int height) {
57 | return this.mRetriever.getScaledFrameAtTime(timeUs, option, width, height);
58 | }
59 |
60 | @Override
61 | public byte[] getEmbeddedPicture() {
62 | return this.mRetriever.getEmbeddedPicture();
63 | }
64 |
65 | @Override
66 | public String extractMetadata(String keyCode) {
67 | return this.mRetriever.extractMetadata(keyCode);
68 | }
69 |
70 | @Override
71 | public void release() {
72 | this.mRetriever.release();
73 | }
74 |
75 | }
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/retriever/impl/MediaMetadataRetrieverImpl.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.retriever.impl;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.media.MediaMetadataRetriever;
6 | import android.net.Uri;
7 |
8 | import com.reone.mrthumb.retriever.IMediaMetadataRetriever;
9 |
10 | import java.util.Map;
11 |
12 | /**
13 | * 基于原生MediaMetadataRetriever实现
14 | * author dengyuhan
15 | * created 2017/5/26 14:49
16 | */
17 | public class MediaMetadataRetrieverImpl implements IMediaMetadataRetriever {
18 | private MediaMetadataRetriever mRetriever;
19 |
20 | public MediaMetadataRetrieverImpl() {
21 | this.mRetriever = new MediaMetadataRetriever();
22 | }
23 |
24 | @Override
25 | public void setDataSource(String path) {
26 | this.mRetriever.setDataSource(path);
27 | }
28 |
29 | @Override
30 | public void setDateSource(String uri, Map headers) {
31 | this.mRetriever.setDataSource(uri, headers);
32 | }
33 |
34 | @Override
35 | public void setDataSource(Context context, Uri uri) {
36 | this.mRetriever.setDataSource(context, uri);
37 | }
38 |
39 | @Override
40 | public Bitmap getFrameAtTime() {
41 | return this.mRetriever.getFrameAtTime();
42 | }
43 |
44 | @Override
45 | public Bitmap getFrameAtTime(long timeUs, int option) {
46 | return this.mRetriever.getFrameAtTime(timeUs, option);
47 | }
48 |
49 | @Override
50 | public Bitmap getScaledFrameAtTime(long timeUs, int width, int height) {
51 | Bitmap atTime = this.mRetriever.getFrameAtTime(timeUs);
52 | if (atTime == null) {
53 | return null;
54 | }
55 | return Bitmap.createScaledBitmap(atTime, width, height, true);
56 | }
57 |
58 | @Override
59 | public Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int height) {
60 | Bitmap atTime = this.mRetriever.getFrameAtTime(timeUs, option);
61 | if (atTime == null) {
62 | return null;
63 | }
64 | return Bitmap.createScaledBitmap(atTime, width, height, true);
65 | }
66 |
67 | @Override
68 | public byte[] getEmbeddedPicture() {
69 | return this.mRetriever.getEmbeddedPicture();
70 | }
71 |
72 | @Override
73 | public String extractMetadata(String keyCode) {
74 | return this.mRetriever.extractMetadata(Integer.parseInt(keyCode));
75 | }
76 |
77 | @Override
78 | public void release() {
79 | this.mRetriever.release();
80 | }
81 | }
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/retriever/transform/BitmapRotateTransform.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.retriever.transform;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.Matrix;
5 |
6 | /**
7 | * author dengyuhan
8 | * created 2017/5/27 13:13
9 | */
10 | public class BitmapRotateTransform {
11 |
12 |
13 | public static Bitmap transform(Bitmap bitmap, float rotate) {
14 | Matrix matrix = new Matrix();
15 | matrix.postRotate(rotate);
16 | int frameWidth = bitmap.getWidth();
17 | int frameHeight = bitmap.getHeight();
18 | return Bitmap.createBitmap(bitmap, 0, 0, frameWidth, frameHeight, matrix, true);
19 | }
20 |
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/retriever/transform/MetadataKey.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.retriever.transform;
2 |
3 | /**
4 | * author dengyuhan
5 | * created 2017/5/27 10:51
6 | */
7 | public class MetadataKey {
8 | private String ffmpegMetadatakey;
9 | private String metadatakey;
10 |
11 | public MetadataKey(String ffmpegMetadatakey, String metadatakey) {
12 | this.ffmpegMetadatakey = ffmpegMetadatakey;
13 | this.metadatakey = metadatakey;
14 | }
15 |
16 | public String getFfmpegMetadatakey() {
17 | return ffmpegMetadatakey;
18 | }
19 |
20 | public void setFfmpegMetadatakey(String ffmpegMetadatakey) {
21 | this.ffmpegMetadatakey = ffmpegMetadatakey;
22 | }
23 |
24 | public String getMetadatakey() {
25 | return metadatakey;
26 | }
27 |
28 | public void setMetadatakey(String metadatakey) {
29 | this.metadatakey = metadatakey;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/retriever/transform/MetadataTransform.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.retriever.transform;
2 |
3 | import android.media.MediaMetadataRetriever;
4 | import android.util.SparseArray;
5 |
6 | import com.reone.mrthumb.retriever.IMediaMetadataRetriever;
7 | import com.reone.mrthumb.retriever.MediaMetadataRetrieverCompat;
8 | import com.reone.mrthumb.retriever.impl.FFmpegMediaMetadataRetrieverImpl;
9 | import com.reone.mrthumb.retriever.impl.MediaMetadataRetrieverImpl;
10 |
11 | import wseemann.media.FFmpegMediaMetadataRetriever;
12 |
13 | /**
14 | * author dengyuhan
15 | * created 2017/5/27 10:39
16 | */
17 | public class MetadataTransform {
18 |
19 | private static SparseArray METADATA_KEYS = new SparseArray<>();
20 |
21 | static {
22 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_ALBUM,
23 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_ALBUM,
24 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUM)));
25 |
26 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_ARTIST,
27 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_ARTIST,
28 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST)));
29 |
30 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_AUTHOR,
31 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_ALBUM_ARTIST,
32 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_AUTHOR)));
33 |
34 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_COMPOSER,
35 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_COMPOSER,
36 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_COMPOSER)));
37 |
38 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_DATE,
39 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_DATE,
40 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_DATE)));
41 |
42 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_TITLE,
43 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_TITLE,
44 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE)));
45 |
46 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_DURATION,
47 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_DURATION,
48 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION)));
49 |
50 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_NUM_TRACKS,
51 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_TRACK,
52 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS)));
53 |
54 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_ALBUMARTIST,
55 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_ALBUM_ARTIST,
56 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST)));
57 |
58 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_DISC_NUMBER,
59 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_DISC,
60 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER)));
61 |
62 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_VIDEO_WIDTH,
63 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH,
64 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)));
65 |
66 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_VIDEO_HEIGHT,
67 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT,
68 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)));
69 |
70 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_VIDEO_ROTATION,
71 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION,
72 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)));
73 |
74 | METADATA_KEYS.put(MediaMetadataRetrieverCompat.METADATA_KEY_CAPTURE_FRAMERATE,
75 | new MetadataKey(FFmpegMediaMetadataRetriever.METADATA_KEY_FRAMERATE,
76 | String.valueOf(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE)));
77 |
78 | }
79 |
80 | public static String transform(Class extends IMediaMetadataRetriever> clazz, int metadataKeyCode) {
81 | MetadataKey metadataKey = METADATA_KEYS.get(metadataKeyCode);
82 | if (clazz.getName().equals(FFmpegMediaMetadataRetrieverImpl.class.getName())) {
83 | return metadataKey.getFfmpegMetadatakey();
84 | } else if (clazz.getName().equals(MediaMetadataRetrieverImpl.class.getName())) {
85 | return metadataKey.getMetadatakey();
86 | }
87 | return null;
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/type/CacheType.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.type;
2 |
3 | import android.support.annotation.IntDef;
4 |
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 |
8 | @IntDef({
9 | CacheType.DISPERSION,
10 | CacheType.ORDER
11 | })
12 | @Retention(RetentionPolicy.SOURCE)
13 | public @interface CacheType {
14 | int DISPERSION = 0;
15 | int ORDER = 1;
16 | }
17 |
--------------------------------------------------------------------------------
/mrthumb/src/main/java/com/reone/mrthumb/type/RetrieverType.java:
--------------------------------------------------------------------------------
1 | package com.reone.mrthumb.type;
2 |
3 | import android.support.annotation.IntDef;
4 |
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 |
8 | @IntDef({
9 | RetrieverType.RETRIEVER_ANDROID,
10 | RetrieverType.RETRIEVER_FFMPEG
11 | })
12 | @Retention(RetentionPolicy.SOURCE)
13 | public @interface RetrieverType {
14 | int RETRIEVER_FFMPEG = 0;
15 | int RETRIEVER_ANDROID = 1;
16 | }
17 |
--------------------------------------------------------------------------------
/mrthumb/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MrthumbLib
3 |
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':simple', ':mrthumb'
2 |
--------------------------------------------------------------------------------
/simple/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/simple/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 27
5 | defaultConfig {
6 | applicationId "com.reone.mrthumbsimple"
7 | minSdkVersion 21
8 | targetSdkVersion 27
9 | versionCode 1
10 | versionName "1.0"
11 | ndk {
12 | abiFilters 'armeabi', 'armeabi-v7a', 'x86'
13 | }
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | implementation fileTree(dir: 'libs', include: ['*.jar'])
25 | implementation 'com.android.support:appcompat-v7:27.1.1'
26 | implementation "com.android.support:recyclerview-v7:27.1.1"
27 | implementation project(path: ':mrthumb')
28 | // implementation "com.github.Reone:Mrthumb:v1.1.0"
29 | implementation 'com.jakewharton:butterknife:8.8.1'
30 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
31 | implementation 'com.github.Reone:Talk:1.0.1'
32 | implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
33 | implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.4'
34 | implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.3'
35 | }
36 |
--------------------------------------------------------------------------------
/simple/preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/preview.gif
--------------------------------------------------------------------------------
/simple/preview2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/preview2.gif
--------------------------------------------------------------------------------
/simple/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 |
--------------------------------------------------------------------------------
/simple/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/simple/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/SimpleActivity.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple;
2 |
3 | import android.graphics.Bitmap;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.support.v7.widget.AppCompatImageView;
7 | import android.view.View;
8 | import android.widget.FrameLayout;
9 | import android.widget.SeekBar;
10 | import android.widget.TextView;
11 |
12 | import com.reone.mrthumb.Mrthumb;
13 | import com.reone.mrthumb.listener.ProcessListener;
14 | import com.reone.simple.player.NiceVideoPlayer;
15 | import com.reone.simple.view.ProgressView;
16 | import com.reone.simple.view.VideoSeekBar;
17 |
18 | import butterknife.BindView;
19 | import butterknife.ButterKnife;
20 |
21 | public class SimpleActivity extends AppCompatActivity {
22 |
23 | protected static final String videoUrl = "http://domhttp.kksmg.com/2018/05/23/ocj_800k_037c50e5c82010c7c57c9f1935462f9c.mp4";
24 | @BindView(R.id.frame_video)
25 | FrameLayout frameVideo;
26 | @BindView(R.id.img_preview)
27 | AppCompatImageView imgPreview;
28 | @BindView(R.id.tv_preview)
29 | TextView tvPreview;
30 | @BindView(R.id.tv_err)
31 | TextView tvErr;
32 | @BindView(R.id.tv_thumb_log_area)
33 | TextView tvThumbLogArea;
34 | @BindView(R.id.tv_player_log_area)
35 | TextView tvPlayerLogArea;
36 | @BindView(R.id.frame_preview)
37 | FrameLayout framePreview;
38 | @BindView(R.id.btn_play)
39 | AppCompatImageView btnPlay;
40 | @BindView(R.id.btn_pause)
41 | AppCompatImageView btnPause;
42 | @BindView(R.id.video_seek_bar)
43 | VideoSeekBar videoSeekBar;
44 | @BindView(R.id.btn_zoom_out)
45 | AppCompatImageView btnZoomOut;
46 | @BindView(R.id.video_loading)
47 | FrameLayout videoLoading;
48 | @BindView(R.id.progress_view)
49 | ProgressView progressView;
50 | //本demo意在展示Mrthumb的使用,所以将无关的操作放在了delegate中
51 | private SimpleActivityDelegate delegate;
52 |
53 | @Override
54 | protected void onCreate(Bundle savedInstanceState) {
55 | super.onCreate(savedInstanceState);
56 | setContentView(R.layout.activity_simple);
57 | ButterKnife.bind(this);
58 | delegate = new SimpleActivityDelegate(this);
59 | delegate.setCallBack(new SimpleActivityDelegate.CallBack() {
60 |
61 | /**
62 | * 拖动进度条过程中回调
63 | */
64 | @Override
65 | public void onSeeking(SeekBar seekBar) {
66 | float percentage = (float) seekBar.getProgress() / seekBar.getMax();
67 | Bitmap bitmap = Mrthumb.obtain().getThumbnail(percentage);
68 | if (bitmap != null && !bitmap.isRecycled()) {
69 | imgPreview.setImageBitmap(bitmap);
70 | imgPreview.setVisibility(View.VISIBLE);
71 | }
72 | }
73 |
74 | /**
75 | * 播放器视频源加载状态回调
76 | */
77 | @Override
78 | public void onPlayStateChanged(int playState, long videoDuration) {
79 | if (playState == NiceVideoPlayer.STATE_PREPARED) {
80 | //视频准备好后开始加载缩略图
81 | Mrthumb.obtain()
82 | // 自定义加载demo可以打开下方注释
83 | // .manager(new ThumbManagerCreator() {
84 | // @Override
85 | // public BaseThumbManager createThumbManager(int count, ArrayList processListeners) {
86 | // return new CustomThumbManager(count, SimpleActivity.this);
87 | // }
88 | // })
89 | .dispersion(true)//todo: 合理可以设置是否采用分散加载,分散加载的好处是可以在滑动的时候有较大的变化
90 | .buffer(videoUrl, videoDuration, Mrthumb.Default.COUNT);
91 | // 更详细的可以调用如下方法
92 | // Mrthumb.obtain().buffer(videoUrl, null, videoDuration, Mrthumb.Default.RETRIEVER_TYPE, Mrthumb.Default.COUNT, Mrthumb.Default.THUMBNAIL_WIDTH, Mrthumb.Default.THUMBNAIL_HEIGHT);
93 | }
94 | }
95 | });
96 | Mrthumb.obtain().addProcessListener(new ProcessListener() {
97 |
98 | @Override
99 | public void onProcess(final int index, final int cacheCount, final int maxCount, final long time, final long duration) {
100 | if (delegate != null) {
101 | progressView.post(new Runnable() {
102 | @Override
103 | public void run() {
104 | progressView.process(index, cacheCount, maxCount, time, duration);
105 | }
106 | });
107 |
108 | delegate.thumbProcessLog("cache " + time / 1000 + "s at " + index + " process:" + (cacheCount * 100 / maxCount) + "%");
109 | }
110 | }
111 | });
112 | }
113 |
114 | @Override
115 | protected void onPause() {
116 | super.onPause();
117 | delegate.onPause();
118 | }
119 |
120 | @Override
121 | protected void onResume() {
122 | super.onResume();
123 | delegate.onResume();
124 | }
125 |
126 | @Override
127 | protected void onDestroy() {
128 | super.onDestroy();
129 | delegate.onDestroy();
130 | Mrthumb.obtain().release();
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/SimpleActivityDelegate.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple;
2 |
3 | import android.os.Handler;
4 | import android.os.Message;
5 | import android.text.TextUtils;
6 | import android.text.method.ScrollingMovementMethod;
7 | import android.view.View;
8 | import android.widget.FrameLayout;
9 | import android.widget.SeekBar;
10 |
11 | import com.reone.simple.player.NiceUtil;
12 | import com.reone.simple.player.NiceVideoPlayer;
13 | import com.reone.simple.player.NiceVideoPlayerController;
14 | import com.reone.simple.player.PlayerState;
15 | import com.reone.simple.view.VideoSeekBar;
16 | import com.reone.talklibrary.TalkApp;
17 |
18 | import java.util.HashMap;
19 |
20 | import static com.reone.simple.SimpleActivity.videoUrl;
21 |
22 | /**
23 | * Created by wangxingsheng on 2018/9/27.
24 | */
25 | public class SimpleActivityDelegate {
26 | private static final int FLAG_PLAYER_LOG = 1;
27 | private static final int FLAG_THUMB_LOG = 2;
28 | private SimpleActivity simpleActivity;
29 | private NiceVideoPlayer videoPlayer;
30 | private Handler mHandler;
31 |
32 | SimpleActivityDelegate(SimpleActivity simpleActivity) {
33 | this.simpleActivity = simpleActivity;
34 | initHandler();
35 | initVideoPlayer();
36 | initView();
37 | }
38 |
39 | private void initHandler() {
40 | mHandler = new Handler(simpleActivity.getMainLooper()) {
41 | @Override
42 | public void handleMessage(Message msg) {
43 | switch (msg.what) {
44 | case FLAG_PLAYER_LOG:
45 | simpleActivity.tvPlayerLogArea.setText(msg.obj.toString());
46 | int offset1 = simpleActivity.tvPlayerLogArea.getLineCount() * simpleActivity.tvPlayerLogArea.getLineHeight();
47 | if (offset1 > simpleActivity.tvPlayerLogArea.getHeight()) {
48 | simpleActivity.tvPlayerLogArea.scrollTo(0, offset1 - simpleActivity.tvPlayerLogArea.getHeight());
49 | }
50 | break;
51 | case FLAG_THUMB_LOG:
52 | simpleActivity.tvThumbLogArea.setText(msg.obj.toString());
53 | int offset2 = simpleActivity.tvThumbLogArea.getLineCount() * simpleActivity.tvThumbLogArea.getLineHeight();
54 | if (offset2 > simpleActivity.tvThumbLogArea.getHeight()) {
55 | simpleActivity.tvThumbLogArea.scrollTo(0, offset2 - simpleActivity.tvThumbLogArea.getHeight());
56 | }
57 | break;
58 | }
59 | }
60 | };
61 | }
62 |
63 | private void initView() {
64 | simpleActivity.tvPlayerLogArea.setMovementMethod(ScrollingMovementMethod.getInstance());
65 | simpleActivity.tvThumbLogArea.setMovementMethod(ScrollingMovementMethod.getInstance());
66 | simpleActivity.btnPlay.setOnClickListener(new View.OnClickListener() {
67 | @Override
68 | public void onClick(View v) {
69 | onBtnPlayClick();
70 | }
71 | });
72 | simpleActivity.btnPause.setOnClickListener(new View.OnClickListener() {
73 | @Override
74 | public void onClick(View v) {
75 | onBtnPauseClick();
76 | }
77 | });
78 | }
79 |
80 | private void initVideoPlayer() {
81 | videoPlayer = new NiceVideoPlayer(simpleActivity);
82 | videoPlayer.setDefaultMute(false);
83 | videoPlayer.setPlayerType(NiceVideoPlayer.TYPE_IJK); // IjkPlayer or MediaPlayer
84 | videoPlayer.setController(new NiceVideoPlayerController(simpleActivity) {
85 | @Override
86 | protected void onPlayStateChanged(int playState) {
87 | if (callBack != null) {
88 | callBack.onPlayStateChanged(playState, videoPlayer.getDuration());
89 | }
90 | if (playState == NiceVideoPlayer.STATE_ERROR) {
91 | simpleActivity.tvErr.setVisibility(VISIBLE);
92 | } else {
93 | simpleActivity.tvErr.setVisibility(GONE);
94 | }
95 | switch (playState) {
96 | case NiceVideoPlayer.STATE_IDLE:
97 | simpleActivity.videoLoading.setVisibility(GONE);
98 | changeNormalBtn(true);
99 | startUpdateProgressTimer();
100 | break;
101 | case NiceVideoPlayer.STATE_PREPARED:
102 | simpleActivity.videoLoading.setVisibility(GONE);
103 | startUpdateProgressTimer();
104 | break;
105 | case NiceVideoPlayer.STATE_PLAYING:
106 | simpleActivity.videoLoading.setVisibility(GONE);
107 | changeNormalBtn(false);
108 | break;
109 | case NiceVideoPlayer.STATE_PAUSED:
110 | simpleActivity.videoLoading.setVisibility(GONE);
111 | changeNormalBtn(true);
112 | break;
113 | case NiceVideoPlayer.STATE_PREPARING:
114 | case NiceVideoPlayer.STATE_BUFFERING_PLAYING:
115 | case NiceVideoPlayer.STATE_BUFFERING_PAUSED:
116 | simpleActivity.videoLoading.setVisibility(VISIBLE);
117 | break;
118 | case NiceVideoPlayer.STATE_ERROR:
119 | case NiceVideoPlayer.STATE_COMPLETED:
120 | cancelUpdateProgressTimer();
121 | reset();
122 | break;
123 | }
124 | playStateLog(playState);
125 | }
126 |
127 | @Override
128 | protected void updateProgress() {
129 | new Handler(simpleActivity.getMainLooper()).post(new Runnable() {
130 | @Override
131 | public void run() {
132 | long position = videoPlayer.getCurrentPosition();
133 | long duration = videoPlayer.getDuration();
134 | int bufferPercentage = videoPlayer.getBufferPercentage();
135 | int progress = (int) (100f * position / duration);
136 | simpleActivity.videoSeekBar.seekbar.setProgress(progress);
137 | simpleActivity.videoSeekBar.seekbar.setSecondaryProgress(bufferPercentage);
138 | simpleActivity.videoSeekBar.seekTime.setText(NiceUtil.formatTime(position));
139 | simpleActivity.videoSeekBar.sumTime.setText(NiceUtil.formatTime(duration));
140 | }
141 | });
142 | }
143 |
144 | @Override
145 | public void reset() {
146 | simpleActivity.videoSeekBar.seekbar.setProgress(0);
147 | simpleActivity.videoSeekBar.seekbar.setSecondaryProgress(0);
148 | simpleActivity.videoSeekBar.seekTime.setText(NiceUtil.formatTime(0));
149 | simpleActivity.videoSeekBar.sumTime.setText(NiceUtil.formatTime(0));
150 | cancelUpdateProgressTimer();
151 | }
152 | });
153 | videoPlayer.continueFromLastPosition(false);
154 | simpleActivity.frameVideo.removeAllViews();
155 | simpleActivity.frameVideo.addView(videoPlayer, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
156 | videoPlayer.setUp(videoUrl, new HashMap());
157 | simpleActivity.videoSeekBar.setSeekBarListener(new VideoSeekBar.SeekBarListener() {
158 | @Override
159 | public void onSeek(SeekBar seekBar) {
160 | if (videoPlayer != null) {
161 | long position = (long) (videoPlayer.getDuration() * seekBar.getProgress() / 100f);
162 | videoPlayer.seekTo(position);
163 | if (videoPlayer.isBufferingPaused() || videoPlayer.isPaused()) {
164 | videoPlayer.restart();
165 | }
166 | }
167 | if (simpleActivity.framePreview != null) {
168 | simpleActivity.framePreview.setVisibility(View.GONE);
169 | }
170 | }
171 |
172 | @Override
173 | public void onSeeking(SeekBar seekBar, int progress) {
174 | if (videoPlayer != null) {
175 | long position = videoPlayer.getDuration() * seekBar.getProgress() / seekBar.getMax();
176 | simpleActivity.tvPreview.setText(NiceUtil.formatTime(position));
177 | if (callBack != null) {
178 | callBack.onSeeking(seekBar);
179 | }
180 | }
181 | }
182 |
183 | @Override
184 | public void onSeekStart() {
185 | if (simpleActivity.framePreview != null) {
186 | simpleActivity.framePreview.setVisibility(View.VISIBLE);
187 | }
188 | }
189 | });
190 | }
191 |
192 | public void onResume() {
193 | if (videoPlayer != null && (videoPlayer.isBufferingPaused() || videoPlayer.isPaused())) {
194 | videoPlayer.restart();
195 | }
196 | }
197 |
198 | public void onPause() {
199 | if (videoPlayer != null) {
200 | videoPlayer.pause();
201 | }
202 | }
203 |
204 | private void playStateLog(@PlayerState int playState) {
205 | StringBuilder sb = new StringBuilder();
206 | if (!TextUtils.isEmpty(simpleActivity.tvPlayerLogArea.getText())) {
207 | sb.append(simpleActivity.tvPlayerLogArea.getText().toString());
208 | sb.append("\n");
209 | }
210 | switch (playState) {
211 | case NiceVideoPlayer.STATE_BUFFERING_PAUSED:
212 | sb.append("STATE_BUFFERING_PAUSED");
213 | break;
214 | case NiceVideoPlayer.STATE_BUFFERING_PLAYING:
215 | sb.append("STATE_BUFFERING_PLAYING");
216 | break;
217 | case NiceVideoPlayer.STATE_COMPLETED:
218 | sb.append("STATE_COMPLETED");
219 | break;
220 | case NiceVideoPlayer.STATE_ERROR:
221 | sb.append("STATE_ERROR");
222 | break;
223 | case NiceVideoPlayer.STATE_IDLE:
224 | sb.append("STATE_IDLE");
225 | break;
226 | case NiceVideoPlayer.STATE_PAUSED:
227 | sb.append("STATE_PAUSED");
228 | break;
229 | case NiceVideoPlayer.STATE_PLAYING:
230 | sb.append("STATE_PLAYING");
231 | break;
232 | case NiceVideoPlayer.STATE_PREPARED:
233 | sb.append("STATE_PREPARED");
234 | break;
235 | case NiceVideoPlayer.STATE_PREPARING:
236 | sb.append("STATE_PREPARING");
237 | break;
238 | }
239 | Message playerMsg = Message.obtain();
240 | playerMsg.what = FLAG_PLAYER_LOG;
241 | playerMsg.obj = sb.toString();
242 | mHandler.sendMessage(playerMsg);
243 | }
244 |
245 | protected void thumbProcessLog(String log) {
246 | StringBuilder sb = new StringBuilder();
247 | if (!TextUtils.isEmpty(simpleActivity.tvThumbLogArea.getText())) {
248 | sb.append(simpleActivity.tvThumbLogArea.getText().toString());
249 | sb.append("\n");
250 | }
251 | sb.append(log);
252 | Message thumbMsg = Message.obtain();
253 | thumbMsg.what = FLAG_THUMB_LOG;
254 | thumbMsg.obj = sb.toString();
255 | mHandler.sendMessage(thumbMsg);
256 | }
257 |
258 | private void changeNormalBtn(boolean showPlayBtn) {
259 | simpleActivity.btnPlay.setVisibility(showPlayBtn ? View.VISIBLE : View.GONE);
260 | simpleActivity.btnPause.setVisibility(showPlayBtn ? View.GONE : View.VISIBLE);
261 | }
262 |
263 | public void onDestroy() {
264 | if (videoPlayer != null) {
265 | videoPlayer.releaseInBackground();
266 | }
267 | }
268 |
269 | public void onBtnPlayClick() {
270 | if (videoPlayer.isIdle()) {
271 | videoPlayer.start();
272 | } else {
273 | videoPlayer.restart();
274 | }
275 | TalkApp.talk("播放");
276 | }
277 |
278 | public void onBtnPauseClick() {
279 | videoPlayer.pause();
280 | TalkApp.talk("暂停");
281 | }
282 |
283 | private CallBack callBack = null;
284 |
285 | public void setCallBack(CallBack callBack) {
286 | this.callBack = callBack;
287 | }
288 |
289 | public interface CallBack {
290 | void onSeeking(SeekBar seekBar);
291 |
292 | void onPlayStateChanged(int playState, long duration);
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/customdemo/CustomProcess.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.customdemo;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | import com.reone.mrthumb.listener.ThumbProvider;
6 | import com.reone.mrthumb.process.CacheProcess;
7 |
8 | /**
9 | * Created by wangxingsheng on 2020/6/15.
10 | * desc:自定义缓存过程
11 | */
12 | public class CustomProcess extends CacheProcess {
13 |
14 | public CustomProcess(ThumbProvider thumbProvider) {
15 | super(thumbProvider);
16 | }
17 |
18 | @Override
19 | public void start() {
20 |
21 | }
22 |
23 | @Override
24 | public Bitmap get(int index) {
25 | return getThumbProvider().getIndex(index);
26 | }
27 |
28 | @Override
29 | public void release() {
30 |
31 | }
32 | }
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/customdemo/CustomThumbManager.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.customdemo;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 |
7 | import com.reone.mrthumb.listener.ThumbProvider;
8 | import com.reone.mrthumb.manager.BaseThumbManager;
9 | import com.reone.mrthumb.process.CacheProcess;
10 | import com.reone.simple.R;
11 |
12 | import java.util.Map;
13 |
14 | /**
15 | * Created by wangxingsheng on 2020/6/15.
16 | * desc:自定义process执行过程
17 | */
18 | public class CustomThumbManager extends BaseThumbManager {
19 |
20 | private Context context;
21 |
22 | public CustomThumbManager(int maxSize, Context context) {
23 | super(maxSize);
24 | this.context = context;
25 | }
26 |
27 | @Override
28 | protected void onThreadStart() {
29 | //如果需要预先执行操作,可以在此处执行操作,此操作在线程中
30 | //方法运行完之后,会执行CacheProcess的start方法
31 | }
32 |
33 | @Override
34 | public void onBufferStart(String url, Map headers, long videoDuration, int retrieverType, int count, int thumbnailWidth, int thumbnailHeight) {
35 | //如果需要预先执行操作,可以在此处执行操作
36 | //此方法在线程线程启动前执行,需要调用super.onBufferStart,以启动缓存线程
37 | super.onBufferStart(url, headers, videoDuration, retrieverType, count, thumbnailWidth, thumbnailHeight);
38 | }
39 |
40 | @Override
41 | protected ThumbProvider getThumbProvider() {
42 | return new ThumbProvider() {
43 | @Override
44 | public Bitmap getIndex(int i) {
45 | //此处用来给CacheProcess提供缩略图数据
46 | return BitmapFactory.decodeResource(context.getResources(), R.mipmap.demo);
47 | }
48 |
49 | @Override
50 | public int maxSize() {
51 | return 0;
52 | }
53 | };
54 | }
55 |
56 | @Override
57 | public CacheProcess getCacheProcess() {
58 | return new CustomProcess(getThumbProvider());
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/player/INiceVideoPlayer.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.player;
2 |
3 | import java.util.Map;
4 |
5 | /**
6 | * Created by XiaoJianjun on 2017/5/5.
7 | * NiceVideoPlayer抽象接口
8 | */
9 | public interface INiceVideoPlayer {
10 |
11 | /**
12 | * 设置视频Url,以及headers
13 | *
14 | * @param url 视频地址,可以是本地,也可以是网络视频
15 | * @param headers 请求header.
16 | */
17 | void setUp(String url, Map headers);
18 |
19 | /**
20 | * 开始播放
21 | */
22 | void start();
23 |
24 | /**
25 | * 从指定的位置开始播放
26 | *
27 | * @param position 播放位置
28 | */
29 | void start(long position);
30 |
31 | /**
32 | * 重新播放,播放器被暂停、播放错误、播放完成后,需要调用此方法重新播放
33 | */
34 | void restart();
35 |
36 | /**
37 | * 暂停播放
38 | */
39 | void pause();
40 |
41 | /**
42 | * seek到制定的位置继续播放
43 | *
44 | * @param pos 播放位置
45 | */
46 | void seekTo(long pos);
47 |
48 | /**
49 | * 设置音量
50 | *
51 | * @param volume 音量值
52 | */
53 | void setVolume(int volume);
54 |
55 | /**
56 | * 设置播放速度,目前只有IjkPlayer有效果,原生MediaPlayer暂不支持
57 | *
58 | * @param speed 播放速度
59 | */
60 | void setSpeed(float speed);
61 |
62 | /**
63 | * 开始播放时,是否从上一次的位置继续播放
64 | *
65 | * @param continueFromLastPosition true 接着上次的位置继续播放,false从头开始播放
66 | */
67 | void continueFromLastPosition(boolean continueFromLastPosition);
68 |
69 | /*********************************
70 | * 以下9个方法是播放器在当前的播放状态
71 | **********************************/
72 | boolean isIdle();
73 |
74 | boolean isPreparing();
75 |
76 | boolean isPrepared();
77 |
78 | boolean isBufferingPlaying();
79 |
80 | boolean isBufferingPaused();
81 |
82 | boolean isPlaying();
83 |
84 | boolean isPaused();
85 |
86 | boolean isError();
87 |
88 | boolean isCompleted();
89 |
90 | /*********************************
91 | * 以下3个方法是播放器的模式
92 | **********************************/
93 | boolean isFullScreen();
94 |
95 | boolean isTinyWindow();
96 |
97 | boolean isNormal();
98 |
99 | boolean isNormalCtrl();
100 |
101 | /**
102 | * 获取最大音量
103 | *
104 | * @return 最大音量值
105 | */
106 | int getMaxVolume();
107 |
108 | /**
109 | * 获取当前音量
110 | *
111 | * @return 当前音量值
112 | */
113 | int getVolume();
114 |
115 | /**
116 | * 获取办法给总时长,毫秒
117 | *
118 | * @return 视频总时长ms
119 | */
120 | long getDuration();
121 |
122 | /**
123 | * 获取当前播放的位置,毫秒
124 | *
125 | * @return 当前播放位置,ms
126 | */
127 | long getCurrentPosition();
128 |
129 | /**
130 | * 获取视频缓冲百分比
131 | *
132 | * @return 缓冲白百分比
133 | */
134 | int getBufferPercentage();
135 |
136 | /**
137 | * 获取播放速度
138 | *
139 | * @param speed 播放速度
140 | * @return 播放速度
141 | */
142 | float getSpeed(float speed);
143 |
144 | /**
145 | * 获取网络加载速度
146 | *
147 | * @return 网络加载速度
148 | */
149 | long getTcpSpeed();
150 |
151 | /**
152 | * 进入全屏模式
153 | */
154 | void enterFullScreen();
155 |
156 | void enterNormalCtrl();
157 |
158 | boolean exitNormalCtrl();
159 |
160 | /**
161 | * 退出全屏模式
162 | *
163 | * @return true 退出
164 | */
165 | boolean exitFullScreen();
166 |
167 | /**
168 | * 进入小窗口模式
169 | */
170 | void enterTinyWindow();
171 |
172 | /**
173 | * 退出小窗口模式
174 | *
175 | * @return true 退出小窗口
176 | */
177 | boolean exitTinyWindow();
178 |
179 | /**
180 | * 此处只释放播放器(如果要释放播放器并恢复控制器状态需要调用{@link #release()}方法)
181 | * 不管是全屏、小窗口还是Normal状态下控制器的UI都不恢复初始状态
182 | * 这样以便在当前播放器状态下可以方便的切换不同的清晰度的视频地址
183 | */
184 | void releasePlayer();
185 |
186 | void reset();
187 |
188 | /**
189 | * 释放INiceVideoPlayer,释放后,内部的播放器被释放掉,同时如果在全屏、小窗口模式下都会退出
190 | * 并且控制器的UI也应该恢复到最初始的状态.
191 | */
192 | void release();
193 |
194 | String getmUrl();
195 | }
196 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/player/LogUtil.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.player;
2 |
3 | import android.util.Log;
4 |
5 | /**
6 | * Created by XiaoJianjun on 2017/5/4.
7 | * log工具.
8 | */
9 | public class LogUtil {
10 |
11 | private static final String TAG = "Mr simple";
12 |
13 | public static void d(String message) {
14 | Log.d(TAG, message);
15 | }
16 |
17 | public static void i(String message) {
18 | Log.i(TAG, message);
19 | }
20 |
21 | public static void e(String message, Throwable throwable) {
22 | Log.e(TAG, message, throwable);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/player/NiceTextureView.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.player;
2 |
3 | import android.content.Context;
4 | import android.view.TextureView;
5 |
6 | /**
7 | * Created by XiaoJianjun on 2017/6/21.
8 | * 重写TextureView,适配视频的宽高和旋转.
9 | * (参考自节操播放器 https://github.com/lipangit/JieCaoVideoPlayer)
10 | */
11 | public class NiceTextureView extends TextureView {
12 |
13 | private int videoHeight;
14 | private int videoWidth;
15 |
16 | public NiceTextureView(Context context) {
17 | super(context);
18 | }
19 |
20 | public void adaptVideoSize(int videoWidth, int videoHeight) {
21 | if (this.videoWidth != videoWidth && this.videoHeight != videoHeight) {
22 | this.videoWidth = videoWidth;
23 | this.videoHeight = videoHeight;
24 | requestLayout();
25 | }
26 | }
27 |
28 | @Override
29 | public void setRotation(float rotation) {
30 | if (rotation != getRotation()) {
31 | super.setRotation(rotation);
32 | requestLayout();
33 | }
34 | }
35 |
36 | @Override
37 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
38 |
39 | float viewRotation = getRotation();
40 |
41 | // 如果判断成立,则说明显示的TextureView和本身的位置是有90度的旋转的,所以需要交换宽高参数。
42 | if (viewRotation == 90f || viewRotation == 270f) {
43 | int tempMeasureSpec = widthMeasureSpec;
44 | widthMeasureSpec = heightMeasureSpec;
45 | heightMeasureSpec = tempMeasureSpec;
46 | }
47 |
48 | int width = getDefaultSize(videoWidth, widthMeasureSpec);
49 | int height = getDefaultSize(videoHeight, heightMeasureSpec);
50 | if (videoWidth > 0 && videoHeight > 0) {
51 |
52 | int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
53 | int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
54 | int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
55 | int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
56 |
57 | if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
58 | // the size is fixed
59 | width = widthSpecSize;
60 | height = heightSpecSize;
61 | // for compatibility, we adjust size based on aspect ratio
62 | if (videoWidth * height < width * videoHeight) {
63 | width = height * videoWidth / videoHeight;
64 | } else if (videoWidth * height > width * videoHeight) {
65 | height = width * videoHeight / videoWidth;
66 | }
67 | } else if (widthSpecMode == MeasureSpec.EXACTLY) {
68 | // only the width is fixed, adjust the height to match aspect ratio if possible
69 | width = widthSpecSize;
70 | height = width * videoHeight / videoWidth;
71 | if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
72 | // couldn't match aspect ratio within the constraints
73 | height = heightSpecSize;
74 | width = height * videoWidth / videoHeight;
75 | }
76 | } else if (heightSpecMode == MeasureSpec.EXACTLY) {
77 | // only the height is fixed, adjust the width to match aspect ratio if possible
78 | height = heightSpecSize;
79 | width = height * videoWidth / videoHeight;
80 | if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
81 | // couldn't match aspect ratio within the constraints
82 | width = widthSpecSize;
83 | height = width * videoHeight / videoWidth;
84 | }
85 | } else {
86 | // neither the width nor the height are fixed, try to use actual video size
87 | width = videoWidth;
88 | height = videoHeight;
89 | if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
90 | // too tall, decrease both width and height
91 | height = heightSpecSize;
92 | width = height * videoWidth / videoHeight;
93 | }
94 | if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
95 | // too wide, decrease both width and height
96 | width = widthSpecSize;
97 | height = width * videoHeight / videoWidth;
98 | }
99 | }
100 | } else {
101 | // no size yet, just adopt the given spec sizes
102 | }
103 | setMeasuredDimension(width, height);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/player/NiceUtil.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.player;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.ContextWrapper;
6 | import android.util.TypedValue;
7 | import android.view.ContextThemeWrapper;
8 | import android.view.WindowManager;
9 |
10 | import java.util.Formatter;
11 | import java.util.Locale;
12 |
13 | /**
14 | * Created by XiaoJianjun on 2017/5/8.
15 | * 工具类.
16 | */
17 | public class NiceUtil {
18 | /**
19 | * Get activity from context object
20 | *
21 | * @param context something
22 | * @return object of Activity or null if it is not Activity
23 | */
24 | public static Activity scanForActivity(Context context) {
25 | if (context == null) return null;
26 | if (context instanceof Activity) {
27 | return (Activity) context;
28 | } else if (context instanceof ContextWrapper) {
29 | return scanForActivity(((ContextWrapper) context).getBaseContext());
30 | }
31 | return null;
32 | }
33 |
34 | /**
35 | * Get AppCompatActivity from context
36 | *
37 | * @param context
38 | * @return AppCompatActivity if it's not null
39 | */
40 | private static Activity getAppCompActivity(Context context) {
41 | if (context == null) return null;
42 | if (context instanceof Activity) {
43 | return (Activity) context;
44 | } else if (context instanceof ContextThemeWrapper) {
45 | return getAppCompActivity(((ContextThemeWrapper) context).getBaseContext());
46 | }
47 | return null;
48 | }
49 |
50 | public static void showActionBar(Context context) {
51 | scanForActivity(context)
52 | .getWindow()
53 | .clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
54 | }
55 |
56 | public static void hideActionBar(Context context) {
57 | scanForActivity(context)
58 | .getWindow()
59 | .setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
60 | WindowManager.LayoutParams.FLAG_FULLSCREEN);
61 | }
62 |
63 | /**
64 | * 获取屏幕宽度
65 | *
66 | * @param context
67 | * @return width of the screen.
68 | */
69 | public static int getScreenWidth(Context context) {
70 | return context.getResources().getDisplayMetrics().widthPixels;
71 | }
72 |
73 | /**
74 | * 获取屏幕高度
75 | *
76 | * @param context
77 | * @return heiht of the screen.
78 | */
79 | public static int getScreenHeight(Context context) {
80 | return context.getResources().getDisplayMetrics().heightPixels;
81 | }
82 |
83 | /**
84 | * dp转px
85 | *
86 | * @param context
87 | * @param dpVal dp value
88 | * @return px value
89 | */
90 | public static int dp2px(Context context, float dpVal) {
91 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal,
92 | context.getResources().getDisplayMetrics());
93 | }
94 |
95 |
96 | /**
97 | * 将px值转换为sp值,保证文字大小不变
98 | *
99 | * @param pxValue
100 | * (DisplayMetrics类中属性scaledDensity)
101 | * @return
102 | */
103 | public static int px2sp(Context context, float pxValue) {
104 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
105 | return (int) (pxValue / fontScale + 0.5f);
106 | }
107 |
108 | /**
109 | * 将sp值转换为px值,保证文字大小不变
110 | *
111 | * @param spValue
112 | * (DisplayMetrics类中属性scaledDensity)
113 | * @return
114 | */
115 | public static int sp2px(Context context, float spValue) {
116 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
117 | return (int) (spValue * fontScale + 0.5f);
118 | }
119 |
120 | /**
121 | * 将毫秒数格式化为"##:##"的时间
122 | *
123 | * @param milliseconds 毫秒数
124 | * @return ##:##
125 | */
126 | public static String formatTime(long milliseconds) {
127 | if (milliseconds <= 0 || milliseconds >= 24 * 60 * 60 * 1000) {
128 | return "00:00";
129 | }
130 | long totalSeconds = milliseconds / 1000;
131 | long seconds = totalSeconds % 60;
132 | long minutes = (totalSeconds / 60) % 60;
133 | long hours = totalSeconds / 3600;
134 | StringBuilder stringBuilder = new StringBuilder();
135 | Formatter mFormatter = new Formatter(stringBuilder, Locale.getDefault());
136 | if (hours > 0) {
137 | return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
138 | } else {
139 | return mFormatter.format("%02d:%02d", minutes, seconds).toString();
140 | }
141 | }
142 |
143 | /**
144 | * 保存播放位置,以便下次播放时接着上次的位置继续播放.
145 | *
146 | * @param context
147 | * @param url 视频链接url
148 | */
149 | public static void savePlayPosition(Context context, String url, long position) {
150 | context.getSharedPreferences("NICE_VIDEO_PALYER_PLAY_POSITION",
151 | Context.MODE_PRIVATE)
152 | .edit()
153 | .putLong(url, position)
154 | .apply();
155 | }
156 |
157 | /**
158 | * 取出上次保存的播放位置
159 | *
160 | * @param context
161 | * @param url 视频链接url
162 | * @return 上次保存的播放位置
163 | */
164 | public static long getSavedPlayPosition(Context context, String url) {
165 | return context.getSharedPreferences("NICE_VIDEO_PALYER_PLAY_POSITION",
166 | Context.MODE_PRIVATE)
167 | .getLong(url, 0);
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/player/NiceVideoPlayer.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.player;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.graphics.SurfaceTexture;
6 | import android.media.AudioManager;
7 | import android.net.Uri;
8 | import android.os.AsyncTask;
9 | import android.text.TextUtils;
10 | import android.util.AttributeSet;
11 | import android.view.Gravity;
12 | import android.view.Surface;
13 | import android.view.TextureView;
14 | import android.view.ViewGroup;
15 | import android.widget.FrameLayout;
16 |
17 | import java.util.Map;
18 |
19 | import tv.danmaku.ijk.media.player.AndroidMediaPlayer;
20 | import tv.danmaku.ijk.media.player.IMediaPlayer;
21 | import tv.danmaku.ijk.media.player.IjkMediaPlayer;
22 |
23 | /**
24 | * Created by wangxingsheng on 2018/8/2.
25 | * 播放器,去除对controller的依赖
26 | */
27 | public class NiceVideoPlayer extends FrameLayout
28 | implements INiceVideoPlayer,
29 | TextureView.SurfaceTextureListener {
30 | /**
31 | * 播放错误
32 | **/
33 | public static final int STATE_ERROR = -1;
34 | /**
35 | * 播放未开始
36 | **/
37 | public static final int STATE_IDLE = 0;
38 | /**
39 | * 播放准备中
40 | **/
41 | public static final int STATE_PREPARING = 1;
42 | /**
43 | * 播放准备就绪
44 | **/
45 | public static final int STATE_PREPARED = 2;
46 | /**
47 | * 正在播放
48 | **/
49 | public static final int STATE_PLAYING = 3;
50 | /**
51 | * 暂停播放
52 | **/
53 | public static final int STATE_PAUSED = 4;
54 | /**
55 | * 正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,缓冲区数据足够后恢复播放)
56 | **/
57 | public static final int STATE_BUFFERING_PLAYING = 5;
58 | /**
59 | * 正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,此时暂停播放器,继续缓冲,缓冲区数据足够后恢复暂停
60 | **/
61 | public static final int STATE_BUFFERING_PAUSED = 6;
62 | /**
63 | * 播放完成
64 | **/
65 | public static final int STATE_COMPLETED = 7;
66 |
67 | /**
68 | * IjkPlayer
69 | **/
70 | public static final int TYPE_IJK = 111;
71 | /**
72 | * MediaPlayer
73 | **/
74 | public static final int TYPE_NATIVE = 222;
75 |
76 | private static AsyncTask releasePlayerTask;
77 | private int mPlayerType = TYPE_IJK;
78 | private int mCurrentState = STATE_IDLE;
79 | private Context mContext;
80 | private AudioManager mAudioManager;
81 | private IMediaPlayer mMediaPlayer;
82 | private FrameLayout mContainer;
83 | private NiceTextureView mTextureView;
84 | private NiceVideoPlayerController mController;
85 | private SurfaceTexture mSurfaceTexture;
86 | private Surface mSurface;
87 | private String mUrl;
88 | private Map mHeaders;
89 | private int mBufferPercentage;
90 | private boolean continueFromLastPosition = true;
91 | private boolean defaultMute = true;
92 | private long skipToPosition;
93 | private IMediaPlayer.OnPreparedListener mOnPreparedListener
94 | = new IMediaPlayer.OnPreparedListener() {
95 | @Override
96 | public void onPrepared(IMediaPlayer mp) {
97 | mCurrentState = STATE_PREPARED;
98 | if (mController != null) {
99 | mController.onPlayStateChanged(mCurrentState);
100 | }
101 | LogUtil.d("onPrepared ——> STATE_PREPARED");
102 | try {
103 | mp.start();
104 | // 从上次的保存位置播放
105 | if (continueFromLastPosition) {
106 | long savedPlayPosition = NiceUtil.getSavedPlayPosition(mContext, mUrl);
107 | mp.seekTo(savedPlayPosition);
108 | }
109 | // 跳到指定位置播放
110 | if (skipToPosition != 0) {
111 | mp.seekTo(skipToPosition);
112 | }
113 | } catch (Exception e) {
114 | e.printStackTrace();
115 | LogUtil.d("mOnPreparedListener ——> e" + e.getMessage());
116 | mCurrentState = STATE_ERROR;
117 | if (mController != null) {
118 | mController.onPlayStateChanged(mCurrentState);
119 | }
120 | }
121 |
122 | }
123 | };
124 | private IMediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener
125 | = new IMediaPlayer.OnVideoSizeChangedListener() {
126 | @Override
127 | public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sar_num, int sar_den) {
128 | mTextureView.adaptVideoSize(width, height);
129 | LogUtil.d("onVideoSizeChanged ——> width:" + width + ", height:" + height);
130 | }
131 | };
132 | private IMediaPlayer.OnCompletionListener mOnCompletionListener
133 | = new IMediaPlayer.OnCompletionListener() {
134 | @Override
135 | public void onCompletion(IMediaPlayer mp) {
136 | mCurrentState = STATE_COMPLETED;
137 | if (mController != null) {
138 | mController.onPlayStateChanged(mCurrentState);
139 | }
140 | LogUtil.d("onCompletion ——> STATE_COMPLETED");
141 | // 清除屏幕常亮
142 | mContainer.setKeepScreenOn(false);
143 | }
144 | };
145 | private IMediaPlayer.OnErrorListener mOnErrorListener
146 | = new IMediaPlayer.OnErrorListener() {
147 | @Override
148 | public boolean onError(IMediaPlayer mp, int what, int extra) {
149 | // 直播流播放时去调用mediaPlayer.getDuration会导致-38和-2147483648错误,忽略该错误
150 | if (what != -38 && what != -2147483648 && extra != -38 && extra != -2147483648) {
151 | mCurrentState = STATE_ERROR;
152 | if (mController != null) {
153 | mController.onPlayStateChanged(mCurrentState);
154 | }
155 | LogUtil.d("onError ——> STATE_ERROR ———— what:" + what + ", extra: " + extra);
156 | }
157 | return true;
158 | }
159 | };
160 | private IMediaPlayer.OnInfoListener mOnInfoListener
161 | = new IMediaPlayer.OnInfoListener() {
162 | @Override
163 | public boolean onInfo(IMediaPlayer mp, int what, int extra) {
164 | if (what == IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
165 | // 播放器开始渲染
166 | mCurrentState = STATE_PLAYING;
167 | if (mController != null) {
168 | mController.onPlayStateChanged(mCurrentState);
169 | }
170 | LogUtil.d("onInfo ——> MEDIA_INFO_VIDEO_RENDERING_START:STATE_PLAYING");
171 | } else if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_START) {
172 | // MediaPlayer暂时不播放,以缓冲更多的数据
173 | if (mCurrentState == STATE_PAUSED || mCurrentState == STATE_BUFFERING_PAUSED) {
174 | mCurrentState = STATE_BUFFERING_PAUSED;
175 | LogUtil.d("onInfo ——> MEDIA_INFO_BUFFERING_START:STATE_BUFFERING_PAUSED");
176 | } else {
177 | mCurrentState = STATE_BUFFERING_PLAYING;
178 | LogUtil.d("onInfo ——> MEDIA_INFO_BUFFERING_START:STATE_BUFFERING_PLAYING");
179 | }
180 | if (mController != null) {
181 | mController.onPlayStateChanged(mCurrentState);
182 | }
183 | } else if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_END) {
184 | // 填充缓冲区后,MediaPlayer恢复播放/暂停
185 | if (mCurrentState == STATE_BUFFERING_PLAYING) {
186 | mCurrentState = STATE_PLAYING;
187 | if (mController != null) {
188 | mController.onPlayStateChanged(mCurrentState);
189 | }
190 | LogUtil.d("onInfo ——> MEDIA_INFO_BUFFERING_END: STATE_PLAYING");
191 | }
192 | if (mCurrentState == STATE_BUFFERING_PAUSED) {
193 | mCurrentState = STATE_PAUSED;
194 | if (mController != null) {
195 | mController.onPlayStateChanged(mCurrentState);
196 | }
197 | LogUtil.d("onInfo ——> MEDIA_INFO_BUFFERING_END: STATE_PAUSED");
198 | }
199 | } else if (what == IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED) {
200 | // 视频旋转了extra度,需要恢复
201 | if (mTextureView != null) {
202 | mTextureView.setRotation(extra);
203 | LogUtil.d("视频旋转角度:" + extra);
204 | }
205 | } else if (what == IMediaPlayer.MEDIA_INFO_NOT_SEEKABLE) {
206 | LogUtil.d("视频不能seekTo,为直播视频");
207 | } else {
208 | LogUtil.d("onInfo ——> what:" + what);
209 | }
210 | return true;
211 | }
212 | };
213 | private IMediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener
214 | = new IMediaPlayer.OnBufferingUpdateListener() {
215 | @Override
216 | public void onBufferingUpdate(IMediaPlayer mp, int percent) {
217 | mBufferPercentage = percent;
218 | }
219 | };
220 |
221 | public NiceVideoPlayer(Context context) {
222 | this(context, null);
223 | }
224 |
225 | public NiceVideoPlayer(Context context, AttributeSet attrs) {
226 | super(context, attrs);
227 | mContext = context;
228 | init();
229 | }
230 |
231 | private void init() {
232 | mContainer = new FrameLayout(mContext);
233 | mContainer.setBackgroundColor(Color.BLACK);
234 | LayoutParams params = new LayoutParams(
235 | ViewGroup.LayoutParams.MATCH_PARENT,
236 | ViewGroup.LayoutParams.MATCH_PARENT);
237 | this.addView(mContainer, params);
238 | }
239 |
240 | public void setUp(String url, Map headers) {
241 | if (TextUtils.equals(mUrl, url)) {
242 | return;
243 | }
244 | mUrl = url;
245 | mHeaders = headers;
246 | }
247 |
248 | public void setController(NiceVideoPlayerController controller) {
249 | if (controller != null) {
250 | mController = controller;
251 | mController.reset();
252 | mController.onPlayStateChanged(mCurrentState);
253 | }
254 | }
255 |
256 | /**
257 | * 设置播放器类型
258 | *
259 | * @param playerType IjkPlayer or MediaPlayer.
260 | */
261 | public void setPlayerType(int playerType) {
262 | mPlayerType = playerType;
263 | }
264 |
265 | /**
266 | * 是否从上一次的位置继续播放
267 | *
268 | * @param continueFromLastPosition true从上一次的位置继续播放
269 | */
270 | @Override
271 | public void continueFromLastPosition(boolean continueFromLastPosition) {
272 | this.continueFromLastPosition = continueFromLastPosition;
273 | }
274 |
275 | @Override
276 | public void setSpeed(float speed) {
277 | if (mMediaPlayer instanceof IjkMediaPlayer) {
278 | ((IjkMediaPlayer) mMediaPlayer).setSpeed(speed);
279 | } else {
280 | LogUtil.d("只有IjkPlayer才能设置播放速度");
281 | }
282 | }
283 |
284 | @Override
285 | public void start() {
286 | if (mCurrentState == STATE_IDLE) {
287 | reset();
288 | initAudioManager();
289 | initMediaPlayer();
290 | initTextureView();
291 | addTextureView();
292 | } else {
293 | LogUtil.d("NiceVideoPlayer只有在mCurrentState == STATE_IDLE时才能调用start方法.");
294 | }
295 | }
296 |
297 | @Override
298 | public void start(long position) {
299 | skipToPosition = position;
300 | start();
301 | }
302 |
303 | @Override
304 | public void restart() {
305 | if (mCurrentState == STATE_PAUSED) {
306 | mMediaPlayer.start();
307 | mCurrentState = STATE_PLAYING;
308 | if (mController != null) {
309 | mController.onPlayStateChanged(mCurrentState);
310 | }
311 | LogUtil.d("STATE_PLAYING");
312 | } else if (mCurrentState == STATE_BUFFERING_PAUSED) {
313 | mMediaPlayer.start();
314 | mCurrentState = STATE_BUFFERING_PLAYING;
315 | if (mController != null) {
316 | mController.onPlayStateChanged(mCurrentState);
317 | }
318 | LogUtil.d("STATE_BUFFERING_PLAYING");
319 | } else if (mCurrentState == STATE_COMPLETED || mCurrentState == STATE_ERROR) {
320 | mMediaPlayer.reset();
321 | openMediaPlayer();
322 | } else {
323 | LogUtil.d("NiceVideoPlayer在mCurrentState == " + mCurrentState + "时不能调用restart()方法.");
324 | }
325 | }
326 |
327 | public void changeStart() {
328 | if (mCurrentState == STATE_PLAYING) {
329 | mMediaPlayer.reset();
330 | openMediaPlayer();
331 | } else {
332 | restart();
333 | }
334 | }
335 |
336 | @Override
337 | public void pause() {
338 | if (mCurrentState == STATE_PLAYING) {
339 | mMediaPlayer.pause();
340 | mCurrentState = STATE_PAUSED;
341 | if (mController != null) {
342 | mController.onPlayStateChanged(mCurrentState);
343 | }
344 | LogUtil.d("STATE_PAUSED");
345 | }
346 | if (mCurrentState == STATE_BUFFERING_PLAYING) {
347 | mMediaPlayer.pause();
348 | mCurrentState = STATE_BUFFERING_PAUSED;
349 | if (mController != null) {
350 | mController.onPlayStateChanged(mCurrentState);
351 | }
352 | LogUtil.d("STATE_BUFFERING_PAUSED");
353 | }
354 | }
355 |
356 | @Override
357 | public void seekTo(long pos) {
358 | if (mMediaPlayer != null) {
359 | mMediaPlayer.seekTo(pos);
360 | }
361 | }
362 |
363 | @Override
364 | public boolean isIdle() {
365 | return mCurrentState == STATE_IDLE;
366 | }
367 |
368 | @Override
369 | public boolean isPreparing() {
370 | return mCurrentState == STATE_PREPARING;
371 | }
372 |
373 | @Override
374 | public boolean isPrepared() {
375 | return mCurrentState == STATE_PREPARED;
376 | }
377 |
378 | @Override
379 | public boolean isBufferingPlaying() {
380 | return mCurrentState == STATE_BUFFERING_PLAYING;
381 | }
382 |
383 | @Override
384 | public boolean isBufferingPaused() {
385 | return mCurrentState == STATE_BUFFERING_PAUSED;
386 | }
387 |
388 | @Override
389 | public boolean isPlaying() {
390 | return mCurrentState == STATE_PLAYING;
391 | }
392 |
393 | @Override
394 | public boolean isPaused() {
395 | return mCurrentState == STATE_PAUSED;
396 | }
397 |
398 | @Override
399 | public boolean isError() {
400 | return mCurrentState == STATE_ERROR;
401 | }
402 |
403 | @Override
404 | public boolean isCompleted() {
405 | return mCurrentState == STATE_COMPLETED;
406 | }
407 |
408 | @Override
409 | public boolean isFullScreen() {
410 | return false;
411 | }
412 |
413 | @Override
414 | public boolean isTinyWindow() {
415 | return false;
416 | }
417 |
418 | @Override
419 | public boolean isNormal() {
420 | return false;
421 | }
422 |
423 | @Override
424 | public boolean isNormalCtrl() {
425 | return false;
426 | }
427 |
428 | @Override
429 | public int getMaxVolume() {
430 | if (mAudioManager != null) {
431 | return mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
432 | }
433 | return 0;
434 | }
435 |
436 | @Override
437 | public int getVolume() {
438 | if (mAudioManager != null) {
439 | return mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
440 | }
441 | return 0;
442 | }
443 |
444 | @Override
445 | public void setVolume(int volume) {
446 | if (mAudioManager != null) {
447 | mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
448 | }
449 | }
450 |
451 | @Override
452 | public long getDuration() {
453 | return mMediaPlayer != null ? mMediaPlayer.getDuration() : 0;
454 | }
455 |
456 | @Override
457 | public long getCurrentPosition() {
458 | return mMediaPlayer != null ? mMediaPlayer.getCurrentPosition() : 0;
459 | }
460 |
461 | public void setDefaultMute(boolean mute) {
462 | this.defaultMute = mute;
463 | }
464 |
465 | @Override
466 | public int getBufferPercentage() {
467 | return mBufferPercentage;
468 | }
469 |
470 | @Override
471 | public float getSpeed(float speed) {
472 | if (mMediaPlayer instanceof IjkMediaPlayer) {
473 | return ((IjkMediaPlayer) mMediaPlayer).getSpeed(speed);
474 | }
475 | return 0;
476 | }
477 |
478 | @Override
479 | public long getTcpSpeed() {
480 | if (mMediaPlayer instanceof IjkMediaPlayer) {
481 | return ((IjkMediaPlayer) mMediaPlayer).getTcpSpeed();
482 | }
483 | return 0;
484 | }
485 |
486 | @Override
487 | public void enterFullScreen() {
488 |
489 | }
490 |
491 | @Override
492 | public void enterNormalCtrl() {
493 |
494 | }
495 |
496 | @Override
497 | public boolean exitNormalCtrl() {
498 | return false;
499 | }
500 |
501 | @Override
502 | public boolean exitFullScreen() {
503 | return false;
504 | }
505 |
506 | @Override
507 | public void enterTinyWindow() {
508 |
509 | }
510 |
511 | @Override
512 | public boolean exitTinyWindow() {
513 | return false;
514 | }
515 |
516 | private void initAudioManager() {
517 | if (mAudioManager == null) {
518 | mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
519 | if (mAudioManager != null) {
520 | mAudioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
521 | if (defaultMute) {
522 | mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
523 | }
524 | }
525 | }
526 | }
527 |
528 | private void initMediaPlayer() {
529 | if (mMediaPlayer == null) {
530 | switch (mPlayerType) {
531 | case TYPE_NATIVE:
532 | mMediaPlayer = new AndroidMediaPlayer();
533 | break;
534 | case TYPE_IJK:
535 | default:
536 | mMediaPlayer = new IjkMediaPlayer();
537 | break;
538 | }
539 | mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
540 | }
541 | }
542 |
543 | private void initTextureView() {
544 | if (mTextureView == null) {
545 | mTextureView = new NiceTextureView(mContext);
546 | mTextureView.setSurfaceTextureListener(this);
547 | }
548 | }
549 |
550 | private void addTextureView() {
551 | mContainer.removeView(mTextureView);
552 | LayoutParams params = new LayoutParams(
553 | ViewGroup.LayoutParams.MATCH_PARENT,
554 | ViewGroup.LayoutParams.MATCH_PARENT,
555 | Gravity.CENTER);
556 | mContainer.addView(mTextureView, params);
557 | }
558 |
559 | private void openMediaPlayer() {
560 | // 屏幕常亮
561 | mContainer.setKeepScreenOn(true);
562 | // 设置dataSource
563 | try {
564 | // 设置监听
565 | mMediaPlayer.setOnPreparedListener(mOnPreparedListener);
566 | mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);
567 | mMediaPlayer.setOnCompletionListener(mOnCompletionListener);
568 | mMediaPlayer.setOnErrorListener(mOnErrorListener);
569 | mMediaPlayer.setOnInfoListener(mOnInfoListener);
570 | mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener);
571 | mMediaPlayer.setDataSource(mContext.getApplicationContext(), Uri.parse(mUrl), mHeaders);
572 | if (mSurface == null) {
573 | mSurface = new Surface(mSurfaceTexture);
574 | }
575 | mMediaPlayer.setSurface(mSurface);
576 | mMediaPlayer.prepareAsync();
577 | mCurrentState = STATE_PREPARING;
578 | if (mController != null) {
579 | mController.onPlayStateChanged(mCurrentState);
580 | }
581 | LogUtil.d("STATE_PREPARING");
582 | } catch (Exception e) {
583 | e.printStackTrace();
584 | mCurrentState = STATE_ERROR;
585 | if (mController != null) {
586 | mController.onPlayStateChanged(mCurrentState);
587 | }
588 | LogUtil.e("打开播放器发生错误", e);
589 | }
590 | }
591 |
592 | @Override
593 | public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
594 | if (mSurfaceTexture == null) {
595 | mSurfaceTexture = surfaceTexture;
596 | openMediaPlayer();
597 | } else {
598 | mTextureView.setSurfaceTexture(mSurfaceTexture);
599 | }
600 | }
601 |
602 | @Override
603 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
604 | }
605 |
606 | @Override
607 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
608 | return mSurfaceTexture == null;
609 | }
610 |
611 | @Override
612 | public void onSurfaceTextureUpdated(SurfaceTexture surface) {
613 | }
614 |
615 | @Override
616 | public void releasePlayer() {
617 |
618 | if (mAudioManager != null) {
619 | mAudioManager.abandonAudioFocus(null);
620 | mAudioManager = null;
621 | }
622 | if (mMediaPlayer != null) {
623 | mMediaPlayer.release();
624 | mMediaPlayer = null;
625 | }
626 | mContainer.removeView(mTextureView);
627 | if (mSurface != null) {
628 | mSurface.release();
629 | mSurface = null;
630 | }
631 | if (mSurfaceTexture != null) {
632 | mSurfaceTexture.release();
633 | mSurfaceTexture = null;
634 | }
635 | mCurrentState = STATE_IDLE;
636 | if (mController != null) {
637 | mController.onPlayStateChanged(mCurrentState);
638 | }
639 | }
640 |
641 | @Override
642 | public void reset() {
643 | // 保存播放位置
644 | if (!TextUtils.isEmpty(mUrl)) {
645 | if (isPlaying() || isBufferingPlaying() || isBufferingPaused() || isPaused()) {
646 | NiceUtil.savePlayPosition(mContext, mUrl, getCurrentPosition());
647 | } else if (isCompleted()) {
648 | NiceUtil.savePlayPosition(mContext, mUrl, 0);
649 | }
650 | }
651 | // 释放播放器
652 | releasePlayer();
653 |
654 | // 恢复控制器
655 | if (mController != null) {
656 | mController.reset();
657 | }
658 | }
659 |
660 | @Override
661 | public void release() {
662 | // 退出全屏或小窗口
663 | if (isFullScreen()) {
664 | exitFullScreen();
665 | }
666 | if (isTinyWindow()) {
667 | exitTinyWindow();
668 | }
669 | reset();
670 | Runtime.getRuntime().gc();
671 | }
672 |
673 | public String getmUrl() {
674 | return mUrl;
675 | }
676 |
677 | public void releaseInBackground() {
678 | // 退出全屏或小窗口
679 | if (isFullScreen()) {
680 | exitFullScreen();
681 | }
682 | if (isTinyWindow()) {
683 | exitTinyWindow();
684 | }
685 | // 保存播放位置
686 | if (isPlaying() || isBufferingPlaying() || isBufferingPaused() || isPaused()) {
687 | NiceUtil.savePlayPosition(mContext, mUrl, getCurrentPosition());
688 | } else if (isCompleted()) {
689 | NiceUtil.savePlayPosition(mContext, mUrl, 0);
690 | }
691 | mCurrentState = STATE_IDLE;
692 | // 恢复控制器
693 | if (mController != null) {
694 | mController.onPlayStateChanged(mCurrentState);
695 | mController.reset();
696 | }
697 | mContainer.removeView(mTextureView);
698 | releasePlayerTask = new ReleasePlayerTask(mAudioManager, mMediaPlayer, mSurfaceTexture, mSurface).execute();
699 | }
700 | }
701 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/player/NiceVideoPlayerController.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.player;
2 |
3 | import android.content.Context;
4 | import android.widget.FrameLayout;
5 |
6 | import java.util.Timer;
7 | import java.util.TimerTask;
8 |
9 | /**
10 | * Created by XiaoJianjun on 2017/6/21.
11 | * 控制器抽象类
12 | */
13 | public abstract class NiceVideoPlayerController extends FrameLayout {
14 |
15 | private Timer mUpdateProgressTimer;
16 | private TimerTask mUpdateProgressTimerTask;
17 |
18 | public NiceVideoPlayerController(Context context) {
19 | super(context);
20 | }
21 |
22 | /**
23 | * 当播放器的播放状态发生变化,在此方法中国你更新不同的播放状态的UI
24 | *
25 | * @param playState 播放状态:
26 | *
27 | * - {@link NiceVideoPlayer#STATE_IDLE}
28 | * - {@link NiceVideoPlayer#STATE_PREPARING}
29 | * - {@link NiceVideoPlayer#STATE_PREPARED}
30 | * - {@link NiceVideoPlayer#STATE_PLAYING}
31 | * - {@link NiceVideoPlayer#STATE_PAUSED}
32 | * - {@link NiceVideoPlayer#STATE_BUFFERING_PLAYING}
33 | * - {@link NiceVideoPlayer#STATE_BUFFERING_PAUSED}
34 | * - {@link NiceVideoPlayer#STATE_ERROR}
35 | * - {@link NiceVideoPlayer#STATE_COMPLETED}
36 | *
37 | */
38 | protected abstract void onPlayStateChanged(@PlayerState int playState);
39 |
40 | /**
41 | * 开启更新进度的计时器。
42 | */
43 | protected void startUpdateProgressTimer() {
44 | cancelUpdateProgressTimer();
45 | if (mUpdateProgressTimer == null) {
46 | mUpdateProgressTimer = new Timer();
47 | }
48 | if (mUpdateProgressTimerTask == null) {
49 | mUpdateProgressTimerTask = new TimerTask() {
50 | @Override
51 | public void run() {
52 | updateProgress();
53 | }
54 | };
55 | }
56 | mUpdateProgressTimer.schedule(mUpdateProgressTimerTask, 0, 1000);
57 | }
58 |
59 | /**
60 | * 取消更新进度的计时器。
61 | */
62 | protected void cancelUpdateProgressTimer() {
63 | if (mUpdateProgressTimer != null) {
64 | mUpdateProgressTimer.cancel();
65 | mUpdateProgressTimer = null;
66 | }
67 | if (mUpdateProgressTimerTask != null) {
68 | mUpdateProgressTimerTask.cancel();
69 | mUpdateProgressTimerTask = null;
70 | }
71 | }
72 |
73 | /**
74 | * 更新进度,包括进度条进度,展示的当前播放位置时长,总时长等。
75 | */
76 | protected abstract void updateProgress();
77 |
78 | public abstract void reset();
79 | }
80 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/player/PlayerState.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.player;
2 |
3 | import android.support.annotation.IntDef;
4 |
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 |
8 | /**
9 | * Created by wangxingsheng on 2018/9/27.
10 | */
11 | @IntDef({NiceVideoPlayer.STATE_IDLE,
12 | NiceVideoPlayer.STATE_PREPARING,
13 | NiceVideoPlayer.STATE_PREPARED,
14 | NiceVideoPlayer.STATE_COMPLETED,
15 | NiceVideoPlayer.STATE_BUFFERING_PLAYING,
16 | NiceVideoPlayer.STATE_BUFFERING_PAUSED,
17 | NiceVideoPlayer.STATE_PLAYING,
18 | NiceVideoPlayer.STATE_PAUSED,
19 | NiceVideoPlayer.STATE_ERROR
20 | })
21 | @Retention(RetentionPolicy.SOURCE)
22 | public @interface PlayerState {
23 | }
24 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/player/ReleasePlayerTask.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.player;
2 |
3 | import android.graphics.SurfaceTexture;
4 | import android.media.AudioManager;
5 | import android.os.AsyncTask;
6 | import android.view.Surface;
7 |
8 | import java.lang.ref.WeakReference;
9 |
10 | import tv.danmaku.ijk.media.player.IMediaPlayer;
11 |
12 | /**
13 | * Created by wangxingsheng on 2018/6/6.
14 | *
15 | * 使用弱引用回收播放器使用资源
16 | */
17 | public class ReleasePlayerTask extends AsyncTask {
18 | private WeakReference audioManagerWeakReference;
19 | private WeakReference mediaPlayerWeakReference;
20 | private WeakReference surfaceTextureWeakReference;
21 | private WeakReference surfaceWeakReference;
22 |
23 | public ReleasePlayerTask(AudioManager audioManager, IMediaPlayer mediaPlayer, SurfaceTexture surfaceTexture, Surface surface) {
24 | this.audioManagerWeakReference = new WeakReference<>(audioManager);
25 | this.mediaPlayerWeakReference = new WeakReference<>(mediaPlayer);
26 | this.surfaceTextureWeakReference = new WeakReference<>(surfaceTexture);
27 | this.surfaceWeakReference = new WeakReference<>(surface);
28 | }
29 |
30 | @Override
31 | protected Object doInBackground(Object[] objects) {
32 | LogUtil.d("ReleasePlayerTask doInBackground");
33 | AudioManager audioManager = audioManagerWeakReference.get();
34 | if (audioManager != null) {
35 | audioManager.abandonAudioFocus(null);
36 | audioManagerWeakReference.clear();
37 | audioManagerWeakReference = null;
38 | LogUtil.d("ReleasePlayerTask release audioManager");
39 | }
40 | IMediaPlayer iMediaPlayer = mediaPlayerWeakReference.get();
41 | if (iMediaPlayer != null) {
42 | iMediaPlayer.release();
43 | mediaPlayerWeakReference.clear();
44 | mediaPlayerWeakReference = null;
45 | LogUtil.d("ReleasePlayerTask release iMediaPlayer");
46 | }
47 | SurfaceTexture surfaceTexture = surfaceTextureWeakReference.get();
48 | if (surfaceTexture != null) {
49 | surfaceTexture.release();
50 | surfaceTextureWeakReference.clear();
51 | surfaceTextureWeakReference = null;
52 | LogUtil.d("ReleasePlayerTask release surfaceTexture");
53 | }
54 | Surface surface = surfaceWeakReference.get();
55 | if (surface != null) {
56 | surface.release();
57 | surfaceWeakReference.clear();
58 | surfaceWeakReference = null;
59 | LogUtil.d("ReleasePlayerTask release surface");
60 | }
61 | return null;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/view/BaseCustomize.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.view;
2 |
3 | import android.content.res.TypedArray;
4 |
5 | /**
6 | * Created by wangxingsheng on 2018/6/29.
7 | *
8 | * 提供一些自定义所需的方法
9 | *
10 | */
11 | public interface BaseCustomize {
12 | /**
13 | * 自定义布局
14 | * @return
15 | */
16 | int getLayoutResource();
17 |
18 | /**
19 | * 获取自定义属性
20 | * @return
21 | */
22 | int[] getStyleableResource();
23 |
24 | /**
25 | * 初始化view
26 | */
27 | void inflateView();
28 |
29 | /**
30 | * 设置自定义属性
31 | * @param typedArray
32 | */
33 | void customAttr(TypedArray typedArray);
34 |
35 | /**
36 | * 设置自定义事件
37 | */
38 | void initEventAndData();
39 | }
40 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/view/BaseCustomizeFrame.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.view;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.support.annotation.NonNull;
6 | import android.support.annotation.Nullable;
7 | import android.util.AttributeSet;
8 | import android.view.View;
9 | import android.widget.FrameLayout;
10 |
11 | import butterknife.ButterKnife;
12 |
13 | /**
14 | * Created by wangxingsheng on 2018/6/29.
15 | *
16 | * 一般的自定义view可以直接继承此类
17 | */
18 | public class BaseCustomizeFrame extends FrameLayout implements BaseCustomize {
19 | public BaseCustomizeFrame(@NonNull Context context) {
20 | this(context, null);
21 | }
22 |
23 | public BaseCustomizeFrame(@NonNull Context context, @Nullable AttributeSet attrs) {
24 | this(context, attrs, 0);
25 | }
26 |
27 | public BaseCustomizeFrame(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
28 | super(context, attrs, defStyleAttr);
29 | View.inflate(context, getLayoutResource(), this);
30 | ButterKnife.bind(this);
31 | inflateView();
32 | if (attrs != null) {
33 | TypedArray a = context.obtainStyledAttributes(attrs, getStyleableResource());
34 | customAttr(a);
35 | a.recycle();
36 | }
37 | initEventAndData();
38 | }
39 |
40 | @Override
41 | public int getLayoutResource() {
42 | return 0;
43 | }
44 |
45 | @Override
46 | public int[] getStyleableResource() {
47 | return new int[0];
48 | }
49 |
50 | @Override
51 | public void inflateView() {
52 |
53 | }
54 |
55 | @Override
56 | public void customAttr(TypedArray typedArray) {
57 |
58 | }
59 |
60 | @Override
61 | public void initEventAndData() {
62 |
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/view/FiexedLayout.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.view;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.text.TextUtils;
6 | import android.util.AttributeSet;
7 | import android.widget.FrameLayout;
8 |
9 | import com.reone.simple.R;
10 |
11 |
12 | /**
13 | * Created by wangxingsheng on 2018/5/17.
14 | * 自定义宽高比布局
15 | */
16 | public class FiexedLayout extends FrameLayout {
17 |
18 | private int mDemoHeight = -1;
19 | private int mDemoWidth = -1;
20 | private String mStandard = "w";
21 | private Boolean standardH;
22 |
23 | public FiexedLayout(Context context, AttributeSet attrs, int defStyle) {
24 | super(context, attrs, defStyle);
25 | TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FiexedLayout, defStyle, 0);
26 | if(a!=null){
27 | mDemoHeight = a.getInteger(R.styleable.FiexedLayout_demoHeight,-1);
28 | mDemoWidth = a.getInteger(R.styleable.FiexedLayout_demoWidth,-1);
29 | String standard = a.getString(R.styleable.FiexedLayout_standard);
30 | if(!TextUtils.isEmpty(standard) && (standard.equals("w") || standard.equals("h"))){
31 | mStandard = standard;
32 | }
33 | }
34 | }
35 |
36 | public FiexedLayout(Context context, AttributeSet attrs) {
37 | this(context, attrs,0);
38 | }
39 |
40 | public FiexedLayout(Context context) {
41 | this(context,null);
42 | }
43 |
44 | @Override
45 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
46 | setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec));
47 |
48 | // Children are just made to fill our space.
49 | if(mStandard.length() > 0 && mDemoWidth != -1 && mDemoHeight != -1){
50 | boolean standardByWidth;
51 | if(standardH != null){
52 | standardByWidth = !standardH;
53 | }else {
54 | standardByWidth = mStandard.equals("w");
55 | }
56 | if(standardByWidth){//以宽为标准
57 | int childWidthSize = getMeasuredWidth();
58 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize-1, MeasureSpec.EXACTLY);
59 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize * mDemoHeight / mDemoWidth - 1, MeasureSpec.EXACTLY);
60 | }else{//以高为标准
61 | int childheightSize = getMeasuredHeight();
62 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(childheightSize+1, MeasureSpec.EXACTLY);
63 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(childheightSize * mDemoWidth / mDemoHeight - 1, MeasureSpec.EXACTLY);
64 | }
65 | }
66 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
67 | }
68 |
69 | public void setStandardH(Boolean standardH) {
70 | this.standardH = standardH;
71 | }
72 | }
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/view/MrthumbAdapter.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.view;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.support.annotation.NonNull;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 |
11 | import com.reone.simple.R;
12 |
13 | import java.util.List;
14 |
15 | import butterknife.BindView;
16 | import butterknife.ButterKnife;
17 |
18 | /**
19 | * Created by wangxingsheng on 2019-06-13.
20 | * desc:
21 | */
22 |
23 | public class MrthumbAdapter extends RecyclerView.Adapter {
24 | private Context context;
25 | private List data;
26 |
27 | public MrthumbAdapter(List data, Context context) {
28 | this.context = context;
29 | this.data = data;
30 | }
31 |
32 | @NonNull
33 | @Override
34 | public MrthumbViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
35 | return new MrthumbViewHolder(LayoutInflater.from(context).inflate(R.layout.layout_progress_item, parent, false));
36 | }
37 |
38 | public ProgressData getItem(int index) {
39 | if (index >= 0 && index < getItemCount()) {
40 | return data.get(index);
41 | }
42 | return new ProgressData(-2);
43 | }
44 |
45 | @Override
46 | public void onBindViewHolder(@NonNull final MrthumbViewHolder holder, int position) {
47 | ProgressData item = data.get(position);
48 | ViewGroup.LayoutParams param = holder.itemView.getLayoutParams();
49 | param.width = context.getResources().getDisplayMetrics().widthPixels / getItemCount();
50 | holder.itemView.setLayoutParams(param);
51 | switch (item.getState()) {
52 | case -1:
53 | holder.progressItem.setBackgroundColor(Color.RED);
54 | break;
55 | case 0:
56 | holder.progressItem.setBackgroundColor(Color.GRAY);
57 | break;
58 | case 1:
59 | holder.progressItem.setBackgroundColor(Color.GREEN);
60 | break;
61 | default:
62 | holder.progressItem.setBackgroundColor(Color.GRAY);
63 | }
64 | }
65 |
66 | @Override
67 | public int getItemCount() {
68 | return data.size();
69 | }
70 |
71 | static class MrthumbViewHolder extends RecyclerView.ViewHolder {
72 | @BindView(R.id.progress_item)
73 | View progressItem;
74 |
75 | public MrthumbViewHolder(View itemView) {
76 | super(itemView);
77 | ButterKnife.bind(this, itemView);
78 | }
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/view/ProgressData.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.view;
2 |
3 | /**
4 | * Created by wangxingsheng on 2019-06-13.
5 | * desc:
6 | */
7 | public class ProgressData {
8 | private int state;
9 | private long time;
10 |
11 | public ProgressData(int state) {
12 | this.state = state;
13 | }
14 |
15 | public ProgressData(int state, long time) {
16 | this.state = state;
17 | this.time = time;
18 | }
19 |
20 | public int getState() {
21 | return state;
22 | }
23 |
24 | public void setState(int state) {
25 | this.state = state;
26 | }
27 |
28 | public long getTime() {
29 | return time;
30 | }
31 |
32 | public void setTime(long time) {
33 | this.time = time;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/view/ProgressView.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.view;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.support.v7.widget.LinearLayoutManager;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.util.AttributeSet;
9 |
10 | import com.reone.simple.R;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | import butterknife.BindView;
16 |
17 | /**
18 | * Created by wangxingsheng on 2019-06-13.
19 | * desc:为了方便展示缩略图的加载进度
20 | */
21 | public class ProgressView extends BaseCustomizeFrame {
22 |
23 | @BindView(R.id.recycler_view)
24 | RecyclerView recyclerView;
25 | MrthumbAdapter mrthumbAdapter;
26 | private List list;
27 |
28 | public ProgressView(@NonNull Context context) {
29 | super(context);
30 | }
31 |
32 | public ProgressView(@NonNull Context context, @Nullable AttributeSet attrs) {
33 | super(context, attrs);
34 | }
35 |
36 | public ProgressView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
37 | super(context, attrs, defStyleAttr);
38 | }
39 |
40 | @Override
41 | public void initEventAndData() {
42 | list = new ArrayList<>();
43 | mrthumbAdapter = new MrthumbAdapter(list, getContext());
44 | recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), RecyclerView.HORIZONTAL, false));
45 | recyclerView.setAdapter(mrthumbAdapter);
46 | }
47 |
48 | @Override
49 | public int getLayoutResource() {
50 | return R.layout.layout_recycle_view;
51 | }
52 |
53 | /**
54 | * 缩略图加载进度回调
55 | *
56 | * @param index 缩略图加载位置
57 | * @param cacheCount 已缓存数量
58 | * @param maxCount 需要缓存总数
59 | * @param time 缓存缩略图所在秒数
60 | * @param duration 视频总时长
61 | */
62 | public void process(int index, int cacheCount, int maxCount, long time, long duration) {
63 | initList(maxCount);
64 | if (index >= 0 && index < maxCount) {
65 | mrthumbAdapter.getItem(index).setState(1);
66 | mrthumbAdapter.getItem(index).setTime(time);
67 | mrthumbAdapter.notifyItemChanged(index);
68 | }
69 | }
70 |
71 | private void initList(int maxCount) {
72 | if (list.size() < maxCount) {
73 | int count = maxCount - list.size();
74 | for (int i = 0; i < count; i++) {
75 | list.add(new ProgressData(0));
76 | }
77 | recyclerView.setAdapter(mrthumbAdapter);
78 | mrthumbAdapter.notifyDataSetChanged();
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/simple/src/main/java/com/reone/simple/view/VideoSeekBar.java:
--------------------------------------------------------------------------------
1 | package com.reone.simple.view;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.drawable.Drawable;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.Nullable;
8 | import android.util.AttributeSet;
9 | import android.util.TypedValue;
10 | import android.widget.SeekBar;
11 | import android.widget.TextView;
12 |
13 |
14 | import com.reone.simple.R;
15 |
16 | import java.lang.reflect.Field;
17 |
18 | import butterknife.BindView;
19 |
20 | /**
21 | * Created by wangxingsheng on 2018/5/10.
22 | */
23 | public class VideoSeekBar extends BaseCustomizeFrame implements SeekBar.OnSeekBarChangeListener {
24 | @BindView(R.id.seek_time)
25 | public TextView seekTime;
26 |
27 | @BindView(R.id.seekbar)
28 | public SeekBar seekbar;
29 |
30 | @BindView(R.id.sum_time)
31 | public TextView sumTime;
32 | private SeekBarListener listener = null;
33 |
34 | public VideoSeekBar(@NonNull Context context) {
35 | super(context);
36 | }
37 |
38 | public VideoSeekBar(@NonNull Context context, @Nullable AttributeSet attrs) {
39 | super(context, attrs);
40 | }
41 |
42 | public VideoSeekBar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
43 | super(context, attrs, defStyleAttr);
44 | }
45 |
46 | public void setSeekBarListener(SeekBarListener listener) {
47 | this.listener = listener;
48 | }
49 |
50 | @Override
51 | public int getLayoutResource() {
52 | return R.layout.layout_video_seekbar;
53 | }
54 |
55 | @Override
56 | public int[] getStyleableResource() {
57 | return R.styleable.VideoSeekBar;
58 | }
59 |
60 | @Override
61 | public void inflateView() {
62 |
63 | }
64 |
65 | @Override
66 | public void customAttr(TypedArray typedArray) {
67 | float textSize = typedArray.getInteger(R.styleable.VideoSeekBar_vsb_text_size, -1);
68 | if (textSize != -1) {
69 | seekTime.setTextSize(TypedValue.COMPLEX_UNIT_SP,textSize);
70 | sumTime.setTextSize(TypedValue.COMPLEX_UNIT_SP,textSize);
71 | }
72 | int progressHeight = (int) typedArray.getDimension(R.styleable.VideoSeekBar_vsb_seek_bar_progress_height,-1);
73 | if(progressHeight != -1){
74 | try {
75 | Class> superclass = seekbar.getClass().getSuperclass().getSuperclass();
76 | Field mMaxHeight = superclass.getDeclaredField("mMaxHeight");
77 | Field mMinHeight = superclass.getDeclaredField("mMinHeight");
78 | mMaxHeight.setAccessible(true);
79 | mMinHeight.setAccessible(true);
80 | mMaxHeight.set(seekbar,progressHeight);
81 | mMinHeight.set(seekbar,progressHeight);
82 | } catch (Exception e) {
83 | e.printStackTrace();
84 | }
85 | }
86 | Drawable style = typedArray.getDrawable(R.styleable.VideoSeekBar_vsb_seek_bar_style);
87 | if (style != null) {
88 | seekbar.setProgressDrawable(style);
89 | }
90 | }
91 |
92 | @Override
93 | public void initEventAndData() {
94 | seekbar.setOnSeekBarChangeListener(this);
95 | }
96 |
97 | @Override
98 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
99 | if (listener != null) {
100 | listener.onSeeking(seekBar, progress);
101 | }
102 | }
103 |
104 | @Override
105 | public void onStartTrackingTouch(SeekBar seekBar) {
106 | if (listener != null) {
107 | listener.onSeekStart();
108 | }
109 | }
110 |
111 | @Override
112 | public void onStopTrackingTouch(SeekBar seekBar) {
113 | if (listener != null) {
114 | listener.onSeek(seekBar);
115 | }
116 | }
117 |
118 | public interface SeekBarListener {
119 | void onSeek(SeekBar seekBar);
120 |
121 | void onSeeking(SeekBar seekBar, int progress);
122 |
123 | void onSeekStart();
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/simple/src/main/res/anim/rotating_anim_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/simple/src/main/res/drawable/bottom_cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/drawable/bottom_cover.png
--------------------------------------------------------------------------------
/simple/src/main/res/drawable/icon_loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/drawable/icon_loading.png
--------------------------------------------------------------------------------
/simple/src/main/res/drawable/icon_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/drawable/icon_pause.png
--------------------------------------------------------------------------------
/simple/src/main/res/drawable/icon_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/drawable/icon_play.png
--------------------------------------------------------------------------------
/simple/src/main/res/drawable/icon_zoom_in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/drawable/icon_zoom_in.png
--------------------------------------------------------------------------------
/simple/src/main/res/drawable/icon_zoom_out.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/drawable/icon_zoom_out.png
--------------------------------------------------------------------------------
/simple/src/main/res/drawable/pd_video_seekbar.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 |
--------------------------------------------------------------------------------
/simple/src/main/res/drawable/thumb_seekbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
8 |
9 |
10 |
11 | -
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/simple/src/main/res/layout/activity_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
21 |
22 |
23 |
30 |
31 |
37 |
38 |
49 |
50 |
51 |
52 |
60 |
61 |
67 |
68 |
75 |
76 |
87 |
88 |
89 |
90 |
91 |
100 |
101 |
104 |
105 |
112 |
113 |
121 |
122 |
123 |
128 |
129 |
130 |
139 |
140 |
141 |
142 |
143 |
148 |
149 |
159 |
160 |
170 |
--------------------------------------------------------------------------------
/simple/src/main/res/layout/layout_progress_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
--------------------------------------------------------------------------------
/simple/src/main/res/layout/layout_recycle_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/simple/src/main/res/layout/layout_video_seekbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
34 |
35 |
44 |
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-xhdpi/demo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-xhdpi/demo.jpg
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/simple/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Reone/Mrthumb/0003de2c482c806c06c8d7a8c3ebbc94d0b3c4f8/simple/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/simple/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/simple/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/simple/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #69A646
4 |
--------------------------------------------------------------------------------
/simple/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Mrthumb
3 |
4 |
--------------------------------------------------------------------------------
/simple/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
--------------------------------------------------------------------------------