├── .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/v/Reone/Mrthumb.svg)](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 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 | --------------------------------------------------------------------------------