├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot ├── screen1.png └── screen2.png ├── settings.gradle ├── universalvideoview ├── .gitignore ├── bintrayUpload.gradle ├── build.gradle ├── proguard-rules.pro ├── project.properties └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── universalvideoview │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── universalvideoview │ │ ├── OrientationDetector.java │ │ ├── UniversalMediaController.java │ │ └── UniversalVideoView.java │ └── res │ ├── drawable-xhdpi │ ├── uvv_back_btn.png │ ├── uvv_common_ic_loading_icon.png │ ├── uvv_itv_player_play.png │ ├── uvv_on_error.png │ ├── uvv_play_vb_bg.png │ ├── uvv_play_vb_bg_progress.png │ ├── uvv_player_player_btn.png │ ├── uvv_player_scale_btn.png │ ├── uvv_seek_dot.png │ ├── uvv_star_zoom_in.png │ ├── uvv_stop_btn.png │ └── uvv_volume_btn.png │ ├── drawable │ ├── uvv_progress_rotate.xml │ └── uvv_star_play_progress_seek.xml │ ├── layout │ ├── uvv_on_error_layout.xml │ ├── uvv_on_loading_layout.xml │ └── uvv_player_controller.xml │ └── values │ ├── color.xml │ ├── dimens.xml │ ├── strings.xml │ ├── styles.xml │ └── universal_videoview_attrs.xml └── universalvideoviewsample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── universalvideoviewsample │ └── ApplicationTest.java └── main ├── AndroidManifest.xml ├── java └── com │ └── universalvideoviewsample │ └── MainActivity.java └── res ├── layout └── activity_main.xml ├── menu └── menu_main.xml ├── mipmap-hdpi └── ic_launcher.png ├── mipmap-mdpi └── ic_launcher.png ├── mipmap-xhdpi └── ic_launcher.png ├── mipmap-xxhdpi └── ic_launcher.png ├── values-w820dp └── dimens.xml └── values ├── color.xml ├── dimens.xml ├── strings.xml └── styles.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .DS_Store 3 | 4 | /build 5 | /**/*/build 6 | 7 | .idea/ 8 | 9 | # built application files 10 | *.apk 11 | *.ap_ 12 | # files for the dex VM 13 | *.dex 14 | # Java class files 15 | *.class 16 | # generated files 17 | bin/ 18 | gen/ 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | # Eclipse project files 22 | .classpath 23 | .project 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | # Intellij project files 27 | *.iml 28 | *.ipr 29 | *.iws 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android UniversalVideoView 2 | 3 | [中文版说明请点击这里](http://my.oschina.net/u/1403288/blog/522278) 4 | 5 | UniversalVideoView is a Android widget helps playing video easier, which is similar with the Android system native `VideoView`, 6 | but providing more customization feature: fitXY or keeping aspect ratio fullscreen videoView, auto switch to fullscreen on landscape mode, customised control UI... 7 | 8 | ![Sample Screenshot 1](./screenshot/screen1.png) 9 | ![Sample Screenshot 2](./screenshot/screen2.png) 10 | 11 | # Usage 12 | 13 | *For a working implementation of this project see the sample app.* 14 | 15 | 1. add library dependency to your `build.gradle` file. 16 | ```groovy 17 | dependencies { 18 | compile 'com.linsea:universalvideoview:1.1.0@aar' 19 | } 20 | ``` 21 | 2. Include the `UniversalVideoView` and `UniversalMediaController` widget in your layout. This should usually be placed 22 | in the same parent `ViewGroup`, which makes sense when in full screen state. 23 | ```xml 24 | 29 | 30 | 37 | 38 | 43 | 44 | 45 | ``` 46 | 47 | 3. In your `onCreate` method, set the `UniversalMediaController` to the `UniversalVideoView` and implements the `UniversalVideoView.VideoViewCallback` Callback. 48 | ```java 49 | View mBottomLayout; 50 | View mVideoLayout; 51 | UniversalVideoView mVideoView; 52 | UniversalMediaController mMediaController; 53 | 54 | mVideoView = (UniversalVideoView) findViewById(R.id.videoView); 55 | mMediaController = (UniversalMediaController) findViewById(R.id.media_controller); 56 | mVideoView.setMediaController(mMediaController); 57 | 58 | mVideoView.setVideoViewCallback(new UniversalVideoView.VideoViewCallback() { 59 | @Override 60 | public void onScaleChange(boolean isFullscreen) { 61 | this.isFullscreen = isFullscreen; 62 | if (isFullscreen) { 63 | ViewGroup.LayoutParams layoutParams = mVideoLayout.getLayoutParams(); 64 | layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 65 | layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 66 | mVideoLayout.setLayoutParams(layoutParams); 67 | //GONE the unconcerned views to leave room for video and controller 68 | mBottomLayout.setVisibility(View.GONE); 69 | } else { 70 | ViewGroup.LayoutParams layoutParams = mVideoLayout.getLayoutParams(); 71 | layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 72 | layoutParams.height = this.cachedHeight; 73 | mVideoLayout.setLayoutParams(layoutParams); 74 | mBottomLayout.setVisibility(View.VISIBLE); 75 | } 76 | } 77 | 78 | @Override 79 | public void onPause(MediaPlayer mediaPlayer) { // Video pause 80 | Log.d(TAG, "onPause UniversalVideoView callback"); 81 | } 82 | 83 | @Override 84 | public void onStart(MediaPlayer mediaPlayer) { // Video start/resume to play 85 | Log.d(TAG, "onStart UniversalVideoView callback"); 86 | } 87 | 88 | @Override 89 | public void onBufferingStart(MediaPlayer mediaPlayer) {// steam start loading 90 | Log.d(TAG, "onBufferingStart UniversalVideoView callback"); 91 | } 92 | 93 | @Override 94 | public void onBufferingEnd(MediaPlayer mediaPlayer) {// steam end loading 95 | Log.d(TAG, "onBufferingEnd UniversalVideoView callback"); 96 | } 97 | 98 | }); 99 | ``` 100 | 101 | ## Note 102 | 103 | * Support Android Gingerbread V2.3(API Level 9 and above). 104 | * UniversalVideoView does not retain its full state when going into the background. 105 | You should save or restore the state and take care of the [Activity Lifecycle](http://developer.android.com/intl/ko/guide/components/activities.html#Lifecycle). 106 | * You may need to set the `android:configChanges="orientation|keyboardHidden|screenSize"` for your `Activity` in `AndroidManifest.xml` 107 | to prevent the system from recreate the Activity while phone rotation. 108 | 109 | # Customization 110 | ## `UniversalVideoView` attribute 111 | 112 | * `uvv_fitXY`, Video scale to fill the VideoView's dimension or keep Aspect Ratio (default) likes Android framework VideoView. 113 | * `uvv_autoRotation`, auto switch to landscape(fullscreen) or portrait mode according to the orientation sensor. 114 | 115 | ## `UniversalMediaController` attribute 116 | * `uvv_scalable`, show or hide the scale button. if you will not play the video in fullscreen. 117 | 118 | # TODO 119 | * Brightness control on `UniversalMediaController`. 120 | * Volume Control on `UniversalMediaController`. 121 | 122 | # License 123 | 124 | Copyright 2015 The UniversalVideoView author 125 | 126 | Licensed under the Apache License, Version 2.0 (the "License"); 127 | you may not use this file except in compliance with the License. 128 | You may obtain a copy of the License at 129 | 130 | http://www.apache.org/licenses/LICENSE-2.0 131 | 132 | Unless required by applicable law or agreed to in writing, software 133 | distributed under the License is distributed on an "AS IS" BASIS, 134 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 135 | See the License for the specific language governing permissions and 136 | limitations under the License. 137 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.0' 9 | classpath 'com.github.dcendents:android-maven-plugin:1.2' 10 | classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.1" 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 14 15:16:09 CST 2015 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-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /screenshot/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/screenshot/screen1.png -------------------------------------------------------------------------------- /screenshot/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/screenshot/screen2.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':universalvideoview', ':universalvideoviewsample' 2 | -------------------------------------------------------------------------------- /universalvideoview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /universalvideoview/bintrayUpload.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | apply plugin: 'com.jfrog.bintray' 3 | 4 | // load properties 5 | Properties properties = new Properties() 6 | File localPropertiesFile = project.file("local.properties"); 7 | if(localPropertiesFile.exists()){ 8 | properties.load(localPropertiesFile.newDataInputStream()) 9 | } 10 | File projectPropertiesFile = project.file("project.properties"); 11 | if(projectPropertiesFile.exists()){ 12 | properties.load(projectPropertiesFile.newDataInputStream()) 13 | } 14 | 15 | // read properties 16 | def projectName = properties.getProperty("project.name") 17 | def projectGroupId = properties.getProperty("project.groupId") 18 | def projectArtifactId = properties.getProperty("project.artifactId") 19 | def projectVersionName = android.defaultConfig.versionName 20 | def projectPackaging = properties.getProperty("project.packaging") 21 | def projectSiteUrl = properties.getProperty("project.siteUrl") 22 | def projectGitUrl = properties.getProperty("project.gitUrl") 23 | 24 | def developerId = properties.getProperty("developer.id") 25 | def developerName = properties.getProperty("developer.name") 26 | def developerEmail = properties.getProperty("developer.email") 27 | 28 | def bintrayUser = properties.getProperty("bintray.user") 29 | def bintrayApikey = properties.getProperty("bintray.apikey") 30 | 31 | def javadocName = properties.getProperty("javadoc.name") 32 | 33 | group = projectGroupId 34 | 35 | // This generates POM.xml with proper parameters 36 | install { 37 | repositories.mavenInstaller { 38 | pom { 39 | project { 40 | name projectName 41 | groupId projectGroupId 42 | artifactId projectArtifactId 43 | version projectVersionName 44 | packaging projectPackaging 45 | url projectSiteUrl 46 | licenses { 47 | license { 48 | name 'The Apache Software License, Version 2.0' 49 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 50 | } 51 | } 52 | developers { 53 | developer { 54 | id developerId 55 | name developerName 56 | email developerEmail 57 | } 58 | } 59 | scm { 60 | connection projectGitUrl 61 | developerConnection projectGitUrl 62 | url projectSiteUrl 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | // This generates sources.jar 70 | task sourcesJar(type: Jar) { 71 | from android.sourceSets.main.java.srcDirs 72 | classifier = 'sources' 73 | } 74 | 75 | task javadoc(type: Javadoc) { 76 | source = android.sourceSets.main.java.srcDirs 77 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 78 | } 79 | 80 | // This generates javadoc.jar 81 | task javadocJar(type: Jar, dependsOn: javadoc) { 82 | classifier = 'javadoc' 83 | from javadoc.destinationDir 84 | } 85 | 86 | artifacts { 87 | archives javadocJar 88 | archives sourcesJar 89 | } 90 | 91 | // javadoc configuration 92 | javadoc { 93 | options{ 94 | encoding "UTF-8" 95 | charSet 'UTF-8' 96 | author true 97 | version projectVersionName 98 | links "http://docs.oracle.com/javase/7/docs/api" 99 | title javadocName 100 | } 101 | } 102 | 103 | // bintray configuration 104 | bintray { 105 | user = bintrayUser 106 | key = bintrayApikey 107 | configurations = ['archives'] 108 | pkg { 109 | repo = "maven" 110 | name = projectName 111 | websiteUrl = projectSiteUrl 112 | vcsUrl = projectGitUrl 113 | licenses = ["Apache-2.0"] 114 | publish = true 115 | } 116 | } -------------------------------------------------------------------------------- /universalvideoview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 9 9 | targetSdkVersion 23 10 | versionCode 4 11 | versionName "1.1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | compile 'com.android.support:appcompat-v7:23.0.0' 24 | } 25 | 26 | apply from: "bintrayUpload.gradle" 27 | -------------------------------------------------------------------------------- /universalvideoview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\AndroidDev\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /universalvideoview/project.properties: -------------------------------------------------------------------------------- 1 | #project 2 | project.name=UniversalVideoView 3 | project.groupId=com.linsea 4 | project.artifactId=universalvideoview 5 | project.packaging=aar 6 | project.siteUrl=https://github.com/linsea/UniversalVideoView 7 | project.gitUrl=https://github.com/linsea/UniversalVideoView.git 8 | 9 | #javadoc 10 | javadoc.name=UniversalVideoView -------------------------------------------------------------------------------- /universalvideoview/src/androidTest/java/com/universalvideoview/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.universalvideoview; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /universalvideoview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /universalvideoview/src/main/java/com/universalvideoview/OrientationDetector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Author 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.universalvideoview; 18 | 19 | import android.content.Context; 20 | import android.content.pm.ActivityInfo; 21 | import android.hardware.SensorManager; 22 | import android.util.Log; 23 | import android.view.OrientationEventListener; 24 | 25 | 26 | public class OrientationDetector { 27 | 28 | private static final String TAG = "OrientationDetector"; 29 | private static final int HOLDING_THRESHOLD = 1500; 30 | private Context context; 31 | private OrientationEventListener orientationEventListener; 32 | 33 | private int rotationThreshold = 20; 34 | private long holdingTime = 0; 35 | private long lastCalcTime = 0; 36 | private Direction lastDirection = Direction.PORTRAIT; 37 | 38 | private int currentOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;//初始为竖屏 39 | 40 | private OrientationChangeListener listener; 41 | 42 | public OrientationDetector(Context context) { 43 | this.context = context; 44 | } 45 | 46 | 47 | public void setOrientationChangeListener(OrientationChangeListener listener) { 48 | this.listener = listener; 49 | } 50 | 51 | public void enable() { 52 | if (orientationEventListener == null) { 53 | orientationEventListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_UI) { 54 | @Override 55 | public void onOrientationChanged(int orientation) { 56 | Direction currDirection = calcDirection(orientation); 57 | if (currDirection == null) { 58 | return; 59 | } 60 | 61 | if (currDirection != lastDirection) { 62 | resetTime(); 63 | lastDirection = currDirection; 64 | if (BuildConfig.DEBUG) { 65 | Log.d(TAG, String.format("方向改变, 开始计时, 当前是方向为%s", currDirection)); 66 | } 67 | } else { 68 | calcHoldingTime(); 69 | if (holdingTime > HOLDING_THRESHOLD) { 70 | if (currDirection == Direction.LANDSCAPE) { 71 | if (currentOrientation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { 72 | Log.d(TAG, "switch to SCREEN_ORIENTATION_LANDSCAPE"); 73 | currentOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 74 | if (listener != null) { 75 | listener.onOrientationChanged(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, currDirection); 76 | } 77 | } 78 | 79 | } else if (currDirection == Direction.PORTRAIT) { 80 | if (currentOrientation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { 81 | Log.d(TAG, "switch to SCREEN_ORIENTATION_PORTRAIT"); 82 | currentOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 83 | if (listener != null) { 84 | listener.onOrientationChanged(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, currDirection); 85 | } 86 | } 87 | 88 | } else if (currDirection == Direction.REVERSE_PORTRAIT) { 89 | if (currentOrientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) { 90 | Log.d(TAG, "switch to SCREEN_ORIENTATION_REVERSE_PORTRAIT"); 91 | currentOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 92 | if (listener != null) { 93 | listener.onOrientationChanged(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, currDirection); 94 | } 95 | } 96 | 97 | } else if (currDirection == Direction.REVERSE_LANDSCAPE) { 98 | if (currentOrientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { 99 | Log.d(TAG, "switch to SCREEN_ORIENTATION_REVERSE_LANDSCAPE"); 100 | currentOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 101 | if (listener != null) { 102 | listener.onOrientationChanged(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE, currDirection); 103 | } 104 | } 105 | 106 | } 107 | 108 | } 109 | } 110 | 111 | } 112 | }; 113 | } 114 | 115 | orientationEventListener.enable(); 116 | } 117 | 118 | private void calcHoldingTime() { 119 | long current = System.currentTimeMillis(); 120 | if (lastCalcTime == 0) { 121 | lastCalcTime = current; 122 | } 123 | holdingTime += current - lastCalcTime; 124 | // Log.d(TAG, "calcHoldingTime holdingTime=" + holdingTime); 125 | lastCalcTime = current; 126 | } 127 | 128 | private void resetTime() { 129 | holdingTime = lastCalcTime = 0; 130 | } 131 | 132 | private Direction calcDirection(int orientation) { 133 | if (orientation <= rotationThreshold 134 | || orientation >= 360 - rotationThreshold) { 135 | return Direction.PORTRAIT; 136 | } else if (Math.abs(orientation - 180) <= rotationThreshold) { 137 | return Direction.REVERSE_PORTRAIT; 138 | } else if (Math.abs(orientation - 90) <= rotationThreshold) { 139 | return Direction.REVERSE_LANDSCAPE; 140 | } else if (Math.abs(orientation - 270) <= rotationThreshold) { 141 | return Direction.LANDSCAPE; 142 | } 143 | return null; 144 | } 145 | 146 | 147 | public void setInitialDirection(Direction direction) { 148 | lastDirection = direction; 149 | } 150 | 151 | public void disable() { 152 | if (orientationEventListener != null) { 153 | orientationEventListener.disable(); 154 | } 155 | } 156 | 157 | public void setThresholdDegree(int degree) { 158 | rotationThreshold = degree; 159 | } 160 | 161 | public interface OrientationChangeListener { 162 | /*** 163 | * @param screenOrientation ActivityInfo.SCREEN_ORIENTATION_PORTRAIT or ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE 164 | * or ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE or ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT 165 | * @param direction PORTRAIT or REVERSE_PORTRAIT when screenOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 166 | * LANDSCAPE or REVERSE_LANDSCAPE when screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE. 167 | */ 168 | void onOrientationChanged(int screenOrientation, Direction direction); 169 | } 170 | 171 | 172 | public enum Direction { 173 | PORTRAIT, REVERSE_PORTRAIT, LANDSCAPE, REVERSE_LANDSCAPE 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /universalvideoview/src/main/java/com/universalvideoview/UniversalMediaController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Author 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | package com.universalvideoview; 19 | 20 | import android.content.Context; 21 | import android.content.res.TypedArray; 22 | import android.os.Handler; 23 | import android.os.Message; 24 | import android.util.AttributeSet; 25 | import android.view.KeyEvent; 26 | import android.view.LayoutInflater; 27 | import android.view.MotionEvent; 28 | import android.view.View; 29 | import android.view.ViewGroup; 30 | import android.widget.FrameLayout; 31 | import android.widget.ImageButton; 32 | import android.widget.ProgressBar; 33 | import android.widget.SeekBar; 34 | import android.widget.SeekBar.OnSeekBarChangeListener; 35 | import android.widget.TextView; 36 | 37 | import java.util.Formatter; 38 | import java.util.Locale; 39 | 40 | public class UniversalMediaController extends FrameLayout { 41 | 42 | 43 | private UniversalMediaController.MediaPlayerControl mPlayer; 44 | 45 | private Context mContext; 46 | 47 | private ProgressBar mProgress; 48 | 49 | private TextView mEndTime, mCurrentTime; 50 | 51 | private TextView mTitle; 52 | 53 | private boolean mShowing = true; 54 | 55 | private boolean mDragging; 56 | 57 | private boolean mScalable = false; 58 | private boolean mIsFullScreen = false; 59 | // private boolean mFullscreenEnabled = false; 60 | 61 | 62 | private static final int sDefaultTimeout = 3000; 63 | 64 | private static final int STATE_PLAYING = 1; 65 | private static final int STATE_PAUSE = 2; 66 | private static final int STATE_LOADING = 3; 67 | private static final int STATE_ERROR = 4; 68 | private static final int STATE_COMPLETE = 5; 69 | 70 | private int mState = STATE_LOADING; 71 | 72 | 73 | private static final int FADE_OUT = 1; 74 | private static final int SHOW_PROGRESS = 2; 75 | private static final int SHOW_LOADING = 3; 76 | private static final int HIDE_LOADING = 4; 77 | private static final int SHOW_ERROR = 5; 78 | private static final int HIDE_ERROR = 6; 79 | private static final int SHOW_COMPLETE = 7; 80 | private static final int HIDE_COMPLETE = 8; 81 | StringBuilder mFormatBuilder; 82 | 83 | Formatter mFormatter; 84 | 85 | private ImageButton mTurnButton;// 开启暂停按钮 86 | 87 | private ImageButton mScaleButton; 88 | 89 | private View mBackButton;// 返回按钮 90 | 91 | private ViewGroup loadingLayout; 92 | 93 | private ViewGroup errorLayout; 94 | 95 | private View mTitleLayout; 96 | private View mControlLayout; 97 | 98 | private View mCenterPlayButton; 99 | 100 | public UniversalMediaController(Context context, AttributeSet attrs) { 101 | super(context, attrs); 102 | mContext = context; 103 | TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.UniversalMediaController); 104 | mScalable = a.getBoolean(R.styleable.UniversalMediaController_uvv_scalable, false); 105 | a.recycle(); 106 | init(context); 107 | } 108 | 109 | public UniversalMediaController(Context context) { 110 | super(context); 111 | init(context); 112 | } 113 | 114 | private void init(Context context) { 115 | mContext = context; 116 | LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 117 | View viewRoot = inflater.inflate(R.layout.uvv_player_controller, this); 118 | viewRoot.setOnTouchListener(mTouchListener); 119 | initControllerView(viewRoot); 120 | } 121 | 122 | 123 | private void initControllerView(View v) { 124 | mTitleLayout = v.findViewById(R.id.title_part); 125 | mControlLayout = v.findViewById(R.id.control_layout); 126 | loadingLayout = (ViewGroup) v.findViewById(R.id.loading_layout); 127 | errorLayout = (ViewGroup) v.findViewById(R.id.error_layout); 128 | mTurnButton = (ImageButton) v.findViewById(R.id.turn_button); 129 | mScaleButton = (ImageButton) v.findViewById(R.id.scale_button); 130 | mCenterPlayButton = v.findViewById(R.id.center_play_btn); 131 | mBackButton = v.findViewById(R.id.back_btn); 132 | 133 | if (mTurnButton != null) { 134 | mTurnButton.requestFocus(); 135 | mTurnButton.setOnClickListener(mPauseListener); 136 | } 137 | 138 | if (mScalable) { 139 | if (mScaleButton != null) { 140 | mScaleButton.setVisibility(VISIBLE); 141 | mScaleButton.setOnClickListener(mScaleListener); 142 | } 143 | } else { 144 | if (mScaleButton != null) { 145 | mScaleButton.setVisibility(GONE); 146 | } 147 | } 148 | 149 | if (mCenterPlayButton != null) {//重新开始播放 150 | mCenterPlayButton.setOnClickListener(mCenterPlayListener); 151 | } 152 | 153 | if (mBackButton != null) {//返回按钮仅在全屏状态下可见 154 | mBackButton.setOnClickListener(mBackListener); 155 | } 156 | 157 | View bar = v.findViewById(R.id.seekbar); 158 | mProgress = (ProgressBar) bar; 159 | if (mProgress != null) { 160 | if (mProgress instanceof SeekBar) { 161 | SeekBar seeker = (SeekBar) mProgress; 162 | seeker.setOnSeekBarChangeListener(mSeekListener); 163 | } 164 | mProgress.setMax(1000); 165 | } 166 | 167 | mEndTime = (TextView) v.findViewById(R.id.duration); 168 | mCurrentTime = (TextView) v.findViewById(R.id.has_played); 169 | mTitle = (TextView) v.findViewById(R.id.title); 170 | mFormatBuilder = new StringBuilder(); 171 | mFormatter = new Formatter(mFormatBuilder, Locale.getDefault()); 172 | } 173 | 174 | 175 | public void setMediaPlayer(MediaPlayerControl player) { 176 | mPlayer = player; 177 | updatePausePlay(); 178 | } 179 | 180 | /** 181 | * Show the controller on screen. It will go away 182 | * automatically after 3 seconds of inactivity. 183 | */ 184 | public void show() { 185 | show(sDefaultTimeout); 186 | } 187 | 188 | /** 189 | * Disable pause or seek buttons if the stream cannot be paused or seeked. 190 | * This requires the control interface to be a MediaPlayerControlExt 191 | */ 192 | private void disableUnsupportedButtons() { 193 | try { 194 | if (mTurnButton != null && mPlayer != null && !mPlayer.canPause()) { 195 | mTurnButton.setEnabled(false); 196 | } 197 | } catch (IncompatibleClassChangeError ex) { 198 | // We were given an old version of the interface, that doesn't have 199 | // the canPause/canSeekXYZ methods. This is OK, it just means we 200 | // assume the media can be paused and seeked, and so we don't disable 201 | // the buttons. 202 | } 203 | } 204 | 205 | /** 206 | * Show the controller on screen. It will go away 207 | * automatically after 'timeout' milliseconds of inactivity. 208 | * 209 | * @param timeout The timeout in milliseconds. Use 0 to show 210 | * the controller until hide() is called. 211 | */ 212 | public void show(int timeout) {//只负责上下两条bar的显示,不负责中央loading,error,playBtn的显示. 213 | if (!mShowing) { 214 | setProgress(); 215 | if (mTurnButton != null) { 216 | mTurnButton.requestFocus(); 217 | } 218 | disableUnsupportedButtons(); 219 | mShowing = true; 220 | } 221 | updatePausePlay(); 222 | updateBackButton(); 223 | 224 | if (getVisibility() != VISIBLE) { 225 | setVisibility(VISIBLE); 226 | } 227 | if (mTitleLayout.getVisibility() != VISIBLE) { 228 | mTitleLayout.setVisibility(VISIBLE); 229 | } 230 | if (mControlLayout.getVisibility() != VISIBLE) { 231 | mControlLayout.setVisibility(VISIBLE); 232 | } 233 | 234 | // cause the progress bar to be updated even if mShowing 235 | // was already true. This happens, for example, if we're 236 | // paused with the progress bar showing the user hits play. 237 | mHandler.sendEmptyMessage(SHOW_PROGRESS); 238 | 239 | Message msg = mHandler.obtainMessage(FADE_OUT); 240 | if (timeout != 0) { 241 | mHandler.removeMessages(FADE_OUT); 242 | mHandler.sendMessageDelayed(msg, timeout); 243 | } 244 | } 245 | 246 | public boolean isShowing() { 247 | return mShowing; 248 | } 249 | 250 | 251 | public void hide() {//只负责上下两条bar的隐藏,不负责中央loading,error,playBtn的隐藏 252 | if (mShowing) { 253 | mHandler.removeMessages(SHOW_PROGRESS); 254 | mTitleLayout.setVisibility(GONE); 255 | mControlLayout.setVisibility(GONE); 256 | mShowing = false; 257 | } 258 | } 259 | 260 | 261 | private Handler mHandler = new Handler() { 262 | @Override 263 | public void handleMessage(Message msg) { 264 | int pos; 265 | switch (msg.what) { 266 | case FADE_OUT: //1 267 | hide(); 268 | break; 269 | case SHOW_PROGRESS: //2 270 | pos = setProgress(); 271 | if (!mDragging && mShowing && mPlayer != null && mPlayer.isPlaying()) { 272 | msg = obtainMessage(SHOW_PROGRESS); 273 | sendMessageDelayed(msg, 1000 - (pos % 1000)); 274 | } 275 | break; 276 | case SHOW_LOADING: //3 277 | show(); 278 | showCenterView(R.id.loading_layout); 279 | break; 280 | case SHOW_COMPLETE: //7 281 | showCenterView(R.id.center_play_btn); 282 | break; 283 | case SHOW_ERROR: //5 284 | show(); 285 | showCenterView(R.id.error_layout); 286 | break; 287 | case HIDE_LOADING: //4 288 | case HIDE_ERROR: //6 289 | case HIDE_COMPLETE: //8 290 | hide(); 291 | hideCenterView(); 292 | break; 293 | } 294 | } 295 | }; 296 | 297 | private void showCenterView(int resId) { 298 | if (resId == R.id.loading_layout) { 299 | if (loadingLayout.getVisibility() != VISIBLE) { 300 | loadingLayout.setVisibility(VISIBLE); 301 | } 302 | if (mCenterPlayButton.getVisibility() == VISIBLE) { 303 | mCenterPlayButton.setVisibility(GONE); 304 | } 305 | if (errorLayout.getVisibility() == VISIBLE) { 306 | errorLayout.setVisibility(GONE); 307 | } 308 | } else if (resId == R.id.center_play_btn) { 309 | if (mCenterPlayButton.getVisibility() != VISIBLE) { 310 | mCenterPlayButton.setVisibility(VISIBLE); 311 | } 312 | if (loadingLayout.getVisibility() == VISIBLE) { 313 | loadingLayout.setVisibility(GONE); 314 | } 315 | if (errorLayout.getVisibility() == VISIBLE) { 316 | errorLayout.setVisibility(GONE); 317 | } 318 | 319 | } else if (resId == R.id.error_layout) { 320 | if (errorLayout.getVisibility() != VISIBLE) { 321 | errorLayout.setVisibility(VISIBLE); 322 | } 323 | if (mCenterPlayButton.getVisibility() == VISIBLE) { 324 | mCenterPlayButton.setVisibility(GONE); 325 | } 326 | if (loadingLayout.getVisibility() == VISIBLE) { 327 | loadingLayout.setVisibility(GONE); 328 | } 329 | 330 | } 331 | } 332 | 333 | 334 | private void hideCenterView() { 335 | if (mCenterPlayButton.getVisibility() == VISIBLE) { 336 | mCenterPlayButton.setVisibility(GONE); 337 | } 338 | if (errorLayout.getVisibility() == VISIBLE) { 339 | errorLayout.setVisibility(GONE); 340 | } 341 | if (loadingLayout.getVisibility() == VISIBLE) { 342 | loadingLayout.setVisibility(GONE); 343 | } 344 | } 345 | 346 | public void reset() { 347 | mCurrentTime.setText("00:00"); 348 | mEndTime.setText("00:00"); 349 | mProgress.setProgress(0); 350 | mTurnButton.setImageResource(R.drawable.uvv_player_player_btn); 351 | setVisibility(View.VISIBLE); 352 | hideLoading(); 353 | } 354 | 355 | private String stringForTime(int timeMs) { 356 | int totalSeconds = timeMs / 1000; 357 | 358 | int seconds = totalSeconds % 60; 359 | int minutes = (totalSeconds / 60) % 60; 360 | int hours = totalSeconds / 3600; 361 | 362 | mFormatBuilder.setLength(0); 363 | if (hours > 0) { 364 | return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString(); 365 | } else { 366 | return mFormatter.format("%02d:%02d", minutes, seconds).toString(); 367 | } 368 | } 369 | 370 | private int setProgress() { 371 | if (mPlayer == null || mDragging) { 372 | return 0; 373 | } 374 | int position = mPlayer.getCurrentPosition(); 375 | int duration = mPlayer.getDuration(); 376 | if (mProgress != null) { 377 | if (duration > 0) { 378 | // use long to avoid overflow 379 | long pos = 1000L * position / duration; 380 | mProgress.setProgress((int) pos); 381 | } 382 | int percent = mPlayer.getBufferPercentage(); 383 | mProgress.setSecondaryProgress(percent * 10); 384 | } 385 | 386 | if (mEndTime != null) 387 | mEndTime.setText(stringForTime(duration)); 388 | if (mCurrentTime != null) 389 | mCurrentTime.setText(stringForTime(position)); 390 | 391 | return position; 392 | } 393 | 394 | @Override 395 | public boolean onTouchEvent(MotionEvent event) { 396 | switch (event.getAction()) { 397 | case MotionEvent.ACTION_DOWN: 398 | show(0); // show until hide is called 399 | handled = false; 400 | break; 401 | case MotionEvent.ACTION_UP: 402 | if (!handled) { 403 | handled = false; 404 | show(sDefaultTimeout); // start timeout 405 | } 406 | break; 407 | case MotionEvent.ACTION_CANCEL: 408 | hide(); 409 | break; 410 | default: 411 | break; 412 | } 413 | return true; 414 | } 415 | 416 | boolean handled = false; 417 | //如果正在显示,则使之消失 418 | private OnTouchListener mTouchListener = new OnTouchListener() { 419 | public boolean onTouch(View v, MotionEvent event) { 420 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 421 | if (mShowing) { 422 | hide(); 423 | handled = true; 424 | return true; 425 | } 426 | } 427 | return false; 428 | } 429 | }; 430 | 431 | @Override 432 | public boolean onTrackballEvent(MotionEvent ev) { 433 | show(sDefaultTimeout); 434 | return false; 435 | } 436 | 437 | @Override 438 | public boolean dispatchKeyEvent(KeyEvent event) { 439 | int keyCode = event.getKeyCode(); 440 | final boolean uniqueDown = event.getRepeatCount() == 0 441 | && event.getAction() == KeyEvent.ACTION_DOWN; 442 | if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK 443 | || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE 444 | || keyCode == KeyEvent.KEYCODE_SPACE) { 445 | if (uniqueDown) { 446 | doPauseResume(); 447 | show(sDefaultTimeout); 448 | if (mTurnButton != null) { 449 | mTurnButton.requestFocus(); 450 | } 451 | } 452 | return true; 453 | } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { 454 | if (uniqueDown && !mPlayer.isPlaying()) { 455 | mPlayer.start(); 456 | updatePausePlay(); 457 | show(sDefaultTimeout); 458 | } 459 | return true; 460 | } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP 461 | || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { 462 | if (uniqueDown && mPlayer.isPlaying()) { 463 | mPlayer.pause(); 464 | updatePausePlay(); 465 | show(sDefaultTimeout); 466 | } 467 | return true; 468 | } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN 469 | || keyCode == KeyEvent.KEYCODE_VOLUME_UP 470 | || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE 471 | || keyCode == KeyEvent.KEYCODE_CAMERA) { 472 | // don't show the controls for volume adjustment 473 | return super.dispatchKeyEvent(event); 474 | } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) { 475 | if (uniqueDown) { 476 | hide(); 477 | } 478 | return true; 479 | } 480 | 481 | show(sDefaultTimeout); 482 | return super.dispatchKeyEvent(event); 483 | } 484 | 485 | private View.OnClickListener mPauseListener = new View.OnClickListener() { 486 | public void onClick(View v) { 487 | if (mPlayer != null) { 488 | doPauseResume(); 489 | show(sDefaultTimeout); 490 | } 491 | } 492 | }; 493 | 494 | private View.OnClickListener mScaleListener = new View.OnClickListener() { 495 | public void onClick(View v) { 496 | mIsFullScreen = !mIsFullScreen; 497 | updateScaleButton(); 498 | updateBackButton(); 499 | mPlayer.setFullscreen(mIsFullScreen); 500 | } 501 | }; 502 | 503 | //仅全屏时才有返回按钮 504 | private View.OnClickListener mBackListener = new View.OnClickListener() { 505 | public void onClick(View v) { 506 | if (mIsFullScreen) { 507 | mIsFullScreen = false; 508 | updateScaleButton(); 509 | updateBackButton(); 510 | mPlayer.setFullscreen(false); 511 | } 512 | 513 | } 514 | }; 515 | 516 | private View.OnClickListener mCenterPlayListener = new View.OnClickListener() { 517 | public void onClick(View v) { 518 | hideCenterView(); 519 | mPlayer.start(); 520 | } 521 | }; 522 | 523 | private void updatePausePlay() { 524 | if (mPlayer != null && mPlayer.isPlaying()) { 525 | mTurnButton.setImageResource(R.drawable.uvv_stop_btn); 526 | // mCenterPlayButton.setVisibility(GONE); 527 | } else { 528 | mTurnButton.setImageResource(R.drawable.uvv_player_player_btn); 529 | // mCenterPlayButton.setVisibility(VISIBLE); 530 | } 531 | } 532 | 533 | void updateScaleButton() { 534 | if (mIsFullScreen) { 535 | mScaleButton.setImageResource(R.drawable.uvv_star_zoom_in); 536 | } else { 537 | mScaleButton.setImageResource(R.drawable.uvv_player_scale_btn); 538 | } 539 | } 540 | 541 | void toggleButtons(boolean isFullScreen) { 542 | mIsFullScreen = isFullScreen; 543 | updateScaleButton(); 544 | updateBackButton(); 545 | } 546 | 547 | void updateBackButton() { 548 | mBackButton.setVisibility(mIsFullScreen ? View.VISIBLE : View.INVISIBLE); 549 | } 550 | 551 | boolean isFullScreen() { 552 | return mIsFullScreen; 553 | } 554 | 555 | private void doPauseResume() { 556 | if (mPlayer.isPlaying()) { 557 | mPlayer.pause(); 558 | } else { 559 | mPlayer.start(); 560 | } 561 | updatePausePlay(); 562 | } 563 | 564 | 565 | private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { 566 | int newPosition = 0; 567 | 568 | boolean change = false; 569 | 570 | public void onStartTrackingTouch(SeekBar bar) { 571 | if (mPlayer == null) { 572 | return; 573 | } 574 | show(3600000); 575 | 576 | mDragging = true; 577 | mHandler.removeMessages(SHOW_PROGRESS); 578 | } 579 | 580 | public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) { 581 | if (mPlayer == null || !fromuser) { 582 | // We're not interested in programmatically generated changes to 583 | // the progress bar's position. 584 | return; 585 | } 586 | 587 | long duration = mPlayer.getDuration(); 588 | long newposition = (duration * progress) / 1000L; 589 | newPosition = (int) newposition; 590 | change = true; 591 | } 592 | 593 | public void onStopTrackingTouch(SeekBar bar) { 594 | if (mPlayer == null) { 595 | return; 596 | } 597 | if (change) { 598 | mPlayer.seekTo(newPosition); 599 | if (mCurrentTime != null) { 600 | mCurrentTime.setText(stringForTime(newPosition)); 601 | } 602 | } 603 | mDragging = false; 604 | setProgress(); 605 | updatePausePlay(); 606 | show(sDefaultTimeout); 607 | 608 | // Ensure that progress is properly updated in the future, 609 | // the call to show() does not guarantee this because it is a 610 | // no-op if we are already showing. 611 | mShowing = true; 612 | mHandler.sendEmptyMessage(SHOW_PROGRESS); 613 | } 614 | }; 615 | 616 | @Override 617 | public void setEnabled(boolean enabled) { 618 | // super.setEnabled(enabled); 619 | if (mTurnButton != null) { 620 | mTurnButton.setEnabled(enabled); 621 | } 622 | if (mProgress != null) { 623 | mProgress.setEnabled(enabled); 624 | } 625 | if (mScalable) { 626 | mScaleButton.setEnabled(enabled); 627 | } 628 | mBackButton.setEnabled(true);// 全屏状态下右上角的返回键总是可用. 629 | } 630 | 631 | public void showLoading() { 632 | mHandler.sendEmptyMessage(SHOW_LOADING); 633 | } 634 | 635 | public void hideLoading() { 636 | mHandler.sendEmptyMessage(HIDE_LOADING); 637 | } 638 | 639 | public void showError() { 640 | mHandler.sendEmptyMessage(SHOW_ERROR); 641 | } 642 | 643 | public void hideError() { 644 | mHandler.sendEmptyMessage(HIDE_ERROR); 645 | } 646 | 647 | public void showComplete() { 648 | mHandler.sendEmptyMessage(SHOW_COMPLETE); 649 | } 650 | 651 | public void hideComplete() { 652 | mHandler.sendEmptyMessage(HIDE_COMPLETE); 653 | } 654 | 655 | public void setTitle(String titile) { 656 | mTitle.setText(titile); 657 | } 658 | 659 | // public void setFullscreenEnabled(boolean enabled) { 660 | // mFullscreenEnabled = enabled; 661 | // mScaleButton.setVisibility(mIsFullScreen ? VISIBLE : GONE); 662 | // } 663 | 664 | 665 | public void setOnErrorView(int resId) { 666 | errorLayout.removeAllViews(); 667 | LayoutInflater inflater = LayoutInflater.from(mContext); 668 | inflater.inflate(resId, errorLayout, true); 669 | } 670 | 671 | public void setOnErrorView(View onErrorView) { 672 | errorLayout.removeAllViews(); 673 | errorLayout.addView(onErrorView); 674 | } 675 | 676 | public void setOnLoadingView(int resId) { 677 | loadingLayout.removeAllViews(); 678 | LayoutInflater inflater = LayoutInflater.from(mContext); 679 | inflater.inflate(resId, loadingLayout, true); 680 | } 681 | 682 | public void setOnLoadingView(View onLoadingView) { 683 | loadingLayout.removeAllViews(); 684 | loadingLayout.addView(onLoadingView); 685 | } 686 | 687 | public void setOnErrorViewClick(View.OnClickListener onClickListener) { 688 | errorLayout.setOnClickListener(onClickListener); 689 | } 690 | 691 | public interface MediaPlayerControl { 692 | void start(); 693 | 694 | void pause(); 695 | 696 | int getDuration(); 697 | 698 | int getCurrentPosition(); 699 | 700 | void seekTo(int pos); 701 | 702 | boolean isPlaying(); 703 | 704 | int getBufferPercentage(); 705 | 706 | boolean canPause(); 707 | 708 | boolean canSeekBackward(); 709 | 710 | boolean canSeekForward(); 711 | 712 | void closePlayer();//关闭播放视频,使播放器处于idle状态 713 | 714 | void setFullscreen(boolean fullscreen); 715 | 716 | /*** 717 | * 718 | * @param fullscreen 719 | * @param screenOrientation valid only fullscreen=true.values should be one of 720 | * ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, 721 | * ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, 722 | * ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT, 723 | * ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE 724 | */ 725 | void setFullscreen(boolean fullscreen, int screenOrientation); 726 | } 727 | } 728 | -------------------------------------------------------------------------------- /universalvideoview/src/main/java/com/universalvideoview/UniversalVideoView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Author 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | package com.universalvideoview; 19 | 20 | import android.annotation.TargetApi; 21 | import android.app.Activity; 22 | import android.content.Context; 23 | import android.content.pm.ActivityInfo; 24 | import android.content.res.TypedArray; 25 | import android.media.AudioManager; 26 | import android.media.MediaPlayer; 27 | import android.net.Uri; 28 | import android.os.Build; 29 | import android.os.Handler; 30 | import android.os.Message; 31 | import android.util.AttributeSet; 32 | import android.util.Log; 33 | import android.view.KeyEvent; 34 | import android.view.MotionEvent; 35 | import android.view.SurfaceHolder; 36 | import android.view.SurfaceView; 37 | import android.view.ViewGroup; 38 | import android.view.WindowManager; 39 | import android.view.accessibility.AccessibilityEvent; 40 | import android.view.accessibility.AccessibilityNodeInfo; 41 | 42 | import java.io.IOException; 43 | import java.util.Map; 44 | 45 | 46 | public class UniversalVideoView extends SurfaceView 47 | implements UniversalMediaController.MediaPlayerControl,OrientationDetector.OrientationChangeListener{ 48 | private String TAG = "UniversalVideoView"; 49 | // settable by the client 50 | private Uri mUri; 51 | 52 | // all possible internal states 53 | private static final int STATE_ERROR = -1; 54 | private static final int STATE_IDLE = 0; 55 | private static final int STATE_PREPARING = 1; 56 | private static final int STATE_PREPARED = 2; 57 | private static final int STATE_PLAYING = 3; 58 | private static final int STATE_PAUSED = 4; 59 | private static final int STATE_PLAYBACK_COMPLETED = 5; 60 | 61 | // mCurrentState is a VideoView object's current state. 62 | // mTargetState is the state that a method caller intends to reach. 63 | // For instance, regardless the VideoView object's current state, 64 | // calling pause() intends to bring the object to a target state 65 | // of STATE_PAUSED. 66 | private int mCurrentState = STATE_IDLE; 67 | private int mTargetState = STATE_IDLE; 68 | 69 | // All the stuff we need for playing and showing a video 70 | private SurfaceHolder mSurfaceHolder = null; 71 | private MediaPlayer mMediaPlayer = null; 72 | private int mAudioSession; 73 | private int mVideoWidth; 74 | private int mVideoHeight; 75 | private int mSurfaceWidth; 76 | private int mSurfaceHeight; 77 | private UniversalMediaController mMediaController; 78 | private MediaPlayer.OnCompletionListener mOnCompletionListener; 79 | private MediaPlayer.OnPreparedListener mOnPreparedListener; 80 | private int mCurrentBufferPercentage; 81 | private MediaPlayer.OnErrorListener mOnErrorListener; 82 | private MediaPlayer.OnInfoListener mOnInfoListener; 83 | private int mSeekWhenPrepared; // recording the seek position while preparing 84 | private boolean mCanPause; 85 | private boolean mCanSeekBack; 86 | private boolean mCanSeekForward; 87 | private boolean mPreparedBeforeStart; 88 | private Context mContext; 89 | private boolean mFitXY = false; 90 | private boolean mAutoRotation = false; 91 | private int mVideoViewLayoutWidth = 0; 92 | private int mVideoViewLayoutHeight = 0; 93 | 94 | private OrientationDetector mOrientationDetector; 95 | private VideoViewCallback videoViewCallback; 96 | 97 | public UniversalVideoView(Context context) { 98 | this(context,null); 99 | } 100 | 101 | public UniversalVideoView(Context context, AttributeSet attrs) { 102 | this(context, attrs, 0); 103 | } 104 | 105 | public UniversalVideoView(Context context, AttributeSet attrs, int defStyleAttr) { 106 | super(context, attrs, defStyleAttr); 107 | mContext = context; 108 | TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.UniversalVideoView,0,0); 109 | mFitXY = a.getBoolean(R.styleable.UniversalVideoView_uvv_fitXY, false); 110 | mAutoRotation = a.getBoolean(R.styleable.UniversalVideoView_uvv_autoRotation, false); 111 | a.recycle(); 112 | initVideoView(); 113 | } 114 | 115 | @Override 116 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 117 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 118 | if (mFitXY) { 119 | onMeasureFitXY(widthMeasureSpec, heightMeasureSpec); 120 | } else { 121 | onMeasureKeepAspectRatio(widthMeasureSpec, heightMeasureSpec); 122 | } 123 | } 124 | 125 | private void onMeasureFitXY(int widthMeasureSpec, int heightMeasureSpec) { 126 | int width = getDefaultSize(mVideoWidth, widthMeasureSpec); 127 | int height = getDefaultSize(mVideoHeight, heightMeasureSpec); 128 | setMeasuredDimension(width, height); 129 | } 130 | 131 | private void onMeasureKeepAspectRatio(int widthMeasureSpec, int heightMeasureSpec) { 132 | //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", " 133 | // + MeasureSpec.toString(heightMeasureSpec) + ")"); 134 | 135 | int width = getDefaultSize(mVideoWidth, widthMeasureSpec); 136 | int height = getDefaultSize(mVideoHeight, heightMeasureSpec); 137 | if (mVideoWidth > 0 && mVideoHeight > 0) { 138 | 139 | int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 140 | int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 141 | int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 142 | int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 143 | 144 | if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) { 145 | // the size is fixed 146 | width = widthSpecSize; 147 | height = heightSpecSize; 148 | 149 | // for compatibility, we adjust size based on aspect ratio 150 | if ( mVideoWidth * height < width * mVideoHeight ) { 151 | //Log.i("@@@", "image too wide, correcting"); 152 | width = height * mVideoWidth / mVideoHeight; 153 | } else if ( mVideoWidth * height > width * mVideoHeight ) { 154 | //Log.i("@@@", "image too tall, correcting"); 155 | height = width * mVideoHeight / mVideoWidth; 156 | } 157 | } else if (widthSpecMode == MeasureSpec.EXACTLY) { 158 | // only the width is fixed, adjust the height to match aspect ratio if possible 159 | width = widthSpecSize; 160 | height = width * mVideoHeight / mVideoWidth; 161 | if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { 162 | // couldn't match aspect ratio within the constraints 163 | height = heightSpecSize; 164 | } 165 | } else if (heightSpecMode == MeasureSpec.EXACTLY) { 166 | // only the height is fixed, adjust the width to match aspect ratio if possible 167 | height = heightSpecSize; 168 | width = height * mVideoWidth / mVideoHeight; 169 | if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { 170 | // couldn't match aspect ratio within the constraints 171 | width = widthSpecSize; 172 | } 173 | } else { 174 | // neither the width nor the height are fixed, try to use actual video size 175 | width = mVideoWidth; 176 | height = mVideoHeight; 177 | if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { 178 | // too tall, decrease both width and height 179 | height = heightSpecSize; 180 | width = height * mVideoWidth / mVideoHeight; 181 | } 182 | if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { 183 | // too wide, decrease both width and height 184 | width = widthSpecSize; 185 | height = width * mVideoHeight / mVideoWidth; 186 | } 187 | } 188 | } else { 189 | // no size yet, just adopt the given spec sizes 190 | } 191 | setMeasuredDimension(width, height); 192 | } 193 | 194 | @Override 195 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 196 | super.onInitializeAccessibilityEvent(event); 197 | event.setClassName(UniversalVideoView.class.getName()); 198 | } 199 | 200 | @Override 201 | public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 202 | super.onInitializeAccessibilityNodeInfo(info); 203 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 204 | info.setClassName(UniversalVideoView.class.getName()); 205 | } 206 | } 207 | 208 | public int resolveAdjustedSize(int desiredSize, int measureSpec) { 209 | return getDefaultSize(desiredSize, measureSpec); 210 | } 211 | 212 | private void initVideoView() { 213 | mVideoWidth = 0; 214 | mVideoHeight = 0; 215 | getHolder().addCallback(mSHCallback); 216 | getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 217 | setFocusable(true); 218 | setFocusableInTouchMode(true); 219 | requestFocus(); 220 | mCurrentState = STATE_IDLE; 221 | mTargetState = STATE_IDLE; 222 | } 223 | 224 | @Override 225 | public void onOrientationChanged(int screenOrientation, OrientationDetector.Direction direction) { 226 | if (!mAutoRotation) { 227 | return; 228 | } 229 | 230 | if (direction == OrientationDetector.Direction.PORTRAIT) { 231 | setFullscreen(false, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 232 | } else if (direction == OrientationDetector.Direction.REVERSE_PORTRAIT) { 233 | setFullscreen(false, ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); 234 | } else if (direction == OrientationDetector.Direction.LANDSCAPE) { 235 | setFullscreen(true, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 236 | } else if (direction == OrientationDetector.Direction.REVERSE_LANDSCAPE) { 237 | setFullscreen(true, ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE); 238 | } 239 | } 240 | 241 | public void setFitXY(boolean fitXY) { 242 | mFitXY = fitXY; 243 | } 244 | 245 | public void setAutoRotation(boolean auto) { 246 | mAutoRotation = auto; 247 | } 248 | 249 | /** 250 | * Sets video path. 251 | * 252 | * @param path the path of the video. 253 | */ 254 | public void setVideoPath(String path) { 255 | setVideoURI(Uri.parse(path)); 256 | } 257 | 258 | /** 259 | * Sets video URI. 260 | * 261 | * @param uri the URI of the video. 262 | */ 263 | public void setVideoURI(Uri uri) { 264 | setVideoURI(uri, null); 265 | } 266 | 267 | /** 268 | * Sets video URI using specific headers. 269 | * 270 | * @param uri the URI of the video. 271 | * @param headers the headers for the URI request. 272 | * Note that the cross domain redirection is allowed by default, but that can be 273 | * changed with key/value pairs through the headers parameter with 274 | * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value 275 | * to disallow or allow cross domain redirection. 276 | */ 277 | public void setVideoURI(Uri uri, Map headers) { 278 | mUri = uri; 279 | mSeekWhenPrepared = 0; 280 | openVideo(); 281 | requestLayout(); 282 | invalidate(); 283 | } 284 | 285 | 286 | public void stopPlayback() { 287 | if (mMediaPlayer != null) { 288 | mMediaPlayer.stop(); 289 | mMediaPlayer.release(); 290 | mMediaPlayer = null; 291 | mCurrentState = STATE_IDLE; 292 | mTargetState = STATE_IDLE; 293 | } 294 | } 295 | 296 | private void openVideo() { 297 | if (mUri == null || mSurfaceHolder == null) { 298 | // not ready for playback just yet, will try again later 299 | return; 300 | } 301 | AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 302 | am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); 303 | 304 | // we shouldn't clear the target state, because somebody might have 305 | // called start() previously 306 | release(false); 307 | try { 308 | mMediaPlayer = new MediaPlayer(); 309 | 310 | if (mAudioSession != 0) { 311 | mMediaPlayer.setAudioSessionId(mAudioSession); 312 | } else { 313 | mAudioSession = mMediaPlayer.getAudioSessionId(); 314 | } 315 | mMediaPlayer.setOnPreparedListener(mPreparedListener); 316 | mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); 317 | mMediaPlayer.setOnCompletionListener(mCompletionListener); 318 | mMediaPlayer.setOnErrorListener(mErrorListener); 319 | mMediaPlayer.setOnInfoListener(mInfoListener); 320 | mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); 321 | mCurrentBufferPercentage = 0; 322 | mMediaPlayer.setDataSource(mContext, mUri); 323 | mMediaPlayer.setDisplay(mSurfaceHolder); 324 | mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 325 | mMediaPlayer.setScreenOnWhilePlaying(true); 326 | mMediaPlayer.prepareAsync(); 327 | 328 | 329 | // we don't set the target state here either, but preserve the 330 | // target state that was there before. 331 | mCurrentState = STATE_PREPARING; 332 | attachMediaController(); 333 | } catch (IOException ex) { 334 | Log.w(TAG, "Unable to open content: " + mUri, ex); 335 | mCurrentState = STATE_ERROR; 336 | mTargetState = STATE_ERROR; 337 | mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); 338 | } 339 | } 340 | 341 | public void setMediaController(UniversalMediaController controller) { 342 | if (mMediaController != null) { 343 | mMediaController.hide(); 344 | } 345 | mMediaController = controller; 346 | attachMediaController(); 347 | } 348 | 349 | private void attachMediaController() { 350 | if (mMediaPlayer != null && mMediaController != null) { 351 | mMediaController.setMediaPlayer(this); 352 | mMediaController.setEnabled(isInPlaybackState()); 353 | mMediaController.hide(); 354 | } 355 | } 356 | 357 | MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = 358 | new MediaPlayer.OnVideoSizeChangedListener() { 359 | public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { 360 | mVideoWidth = mp.getVideoWidth(); 361 | mVideoHeight = mp.getVideoHeight(); 362 | Log.d(TAG, String.format("onVideoSizeChanged width=%d,height=%d", mVideoWidth, mVideoHeight)); 363 | if (mVideoWidth != 0 && mVideoHeight != 0) { 364 | getHolder().setFixedSize(mVideoWidth, mVideoHeight); 365 | requestLayout(); 366 | } 367 | } 368 | }; 369 | 370 | MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { 371 | public void onPrepared(MediaPlayer mp) { 372 | mCurrentState = STATE_PREPARED; 373 | 374 | mCanPause = mCanSeekBack = mCanSeekForward = true; 375 | 376 | mPreparedBeforeStart = true; 377 | if (mMediaController != null) { 378 | mMediaController.hideLoading(); 379 | } 380 | 381 | if (mOnPreparedListener != null) { 382 | mOnPreparedListener.onPrepared(mMediaPlayer); 383 | } 384 | if (mMediaController != null) { 385 | mMediaController.setEnabled(true); 386 | } 387 | mVideoWidth = mp.getVideoWidth(); 388 | mVideoHeight = mp.getVideoHeight(); 389 | 390 | int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call 391 | if (seekToPosition != 0) { 392 | seekTo(seekToPosition); 393 | } 394 | if (mVideoWidth != 0 && mVideoHeight != 0) { 395 | //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight); 396 | getHolder().setFixedSize(mVideoWidth, mVideoHeight); 397 | if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) { 398 | // We didn't actually change the size (it was already at the size 399 | // we need), so we won't get a "surface changed" callback, so 400 | // start the video here instead of in the callback. 401 | if (mTargetState == STATE_PLAYING) { 402 | start(); 403 | if (mMediaController != null) { 404 | mMediaController.show(); 405 | } 406 | } else if (!isPlaying() && 407 | (seekToPosition != 0 || getCurrentPosition() > 0)) { 408 | if (mMediaController != null) { 409 | // Show the media controls when we're paused into a video and make 'em stick. 410 | mMediaController.show(0); 411 | } 412 | } 413 | } 414 | } else { 415 | // We don't know the video size yet, but should start anyway. 416 | // The video size might be reported to us later. 417 | if (mTargetState == STATE_PLAYING) { 418 | start(); 419 | } 420 | } 421 | } 422 | }; 423 | 424 | private MediaPlayer.OnCompletionListener mCompletionListener = 425 | new MediaPlayer.OnCompletionListener() { 426 | public void onCompletion(MediaPlayer mp) { 427 | mCurrentState = STATE_PLAYBACK_COMPLETED; 428 | mTargetState = STATE_PLAYBACK_COMPLETED; 429 | if (mMediaController != null) { 430 | boolean a = mMediaPlayer.isPlaying(); 431 | int b = mCurrentState; 432 | mMediaController.showComplete(); 433 | //FIXME 播放完成后,视频中央会显示一个播放按钮,点击播放按钮会调用start重播, 434 | // 但start后竟然又回调到这里,导致第一次点击按钮不会播放视频,需要点击第二次. 435 | Log.d(TAG, String.format("a=%s,b=%d", a, b)); 436 | } 437 | if (mOnCompletionListener != null) { 438 | mOnCompletionListener.onCompletion(mMediaPlayer); 439 | } 440 | } 441 | }; 442 | 443 | private MediaPlayer.OnInfoListener mInfoListener = 444 | new MediaPlayer.OnInfoListener() { 445 | public boolean onInfo(MediaPlayer mp, int what, int extra){ 446 | boolean handled = false; 447 | switch (what) { 448 | case MediaPlayer.MEDIA_INFO_BUFFERING_START: 449 | Log.d(TAG, "onInfo MediaPlayer.MEDIA_INFO_BUFFERING_START"); 450 | if (videoViewCallback != null) { 451 | videoViewCallback.onBufferingStart(mMediaPlayer); 452 | } 453 | if (mMediaController != null) { 454 | mMediaController.showLoading(); 455 | } 456 | handled = true; 457 | break; 458 | case MediaPlayer.MEDIA_INFO_BUFFERING_END: 459 | Log.d(TAG, "onInfo MediaPlayer.MEDIA_INFO_BUFFERING_END"); 460 | if (videoViewCallback != null) { 461 | videoViewCallback.onBufferingEnd(mMediaPlayer); 462 | } 463 | if (mMediaController != null) { 464 | mMediaController.hideLoading(); 465 | } 466 | handled = true; 467 | break; 468 | } 469 | if (mOnInfoListener != null) { 470 | return mOnInfoListener.onInfo(mp, what, extra) || handled; 471 | } 472 | return handled; 473 | } 474 | }; 475 | 476 | private MediaPlayer.OnErrorListener mErrorListener = 477 | new MediaPlayer.OnErrorListener() { 478 | public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { 479 | Log.d(TAG, "Error: " + framework_err + "," + impl_err); 480 | mCurrentState = STATE_ERROR; 481 | mTargetState = STATE_ERROR; 482 | if (mMediaController != null) { 483 | mMediaController.showError(); 484 | } 485 | 486 | /* If an error handler has been supplied, use it and finish. */ 487 | if (mOnErrorListener != null) { 488 | if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) { 489 | return true; 490 | } 491 | } 492 | 493 | /* Otherwise, pop up an error dialog so the user knows that 494 | * something bad has happened. Only try and pop up the dialog 495 | * if we're attached to a window. When we're going away and no 496 | * longer have a window, don't bother showing the user an error. 497 | */ 498 | // if (getWindowToken() != null) { 499 | // Resources r = mContext.getResources(); 500 | // int messageId; 501 | // 502 | // if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { 503 | // messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback; 504 | // } else { 505 | // messageId = com.android.internal.R.string.VideoView_error_text_unknown; 506 | // } 507 | // 508 | // new AlertDialog.Builder(mContext) 509 | // .setMessage(messageId) 510 | // .setPositiveButton(com.android.internal.R.string.VideoView_error_button, 511 | // new DialogInterface.OnClickListener() { 512 | // public void onClick(DialogInterface dialog, int whichButton) { 513 | // /* If we get here, there is no onError listener, so 514 | // * at least inform them that the video is over. 515 | // */ 516 | // if (mOnCompletionListener != null) { 517 | // mOnCompletionListener.onCompletion(mMediaPlayer); 518 | // } 519 | // } 520 | // }) 521 | // .setCancelable(false) 522 | // .show(); 523 | // } 524 | return true; 525 | } 526 | }; 527 | 528 | private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = 529 | new MediaPlayer.OnBufferingUpdateListener() { 530 | public void onBufferingUpdate(MediaPlayer mp, int percent) { 531 | mCurrentBufferPercentage = percent; 532 | } 533 | }; 534 | 535 | /** 536 | * Register a callback to be invoked when the media file 537 | * is loaded and ready to go. 538 | * 539 | * @param l The callback that will be run 540 | */ 541 | public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) 542 | { 543 | mOnPreparedListener = l; 544 | } 545 | 546 | /** 547 | * Register a callback to be invoked when the end of a media file 548 | * has been reached during playback. 549 | * 550 | * @param l The callback that will be run 551 | */ 552 | public void setOnCompletionListener(MediaPlayer.OnCompletionListener l) 553 | { 554 | mOnCompletionListener = l; 555 | } 556 | 557 | /** 558 | * Register a callback to be invoked when an error occurs 559 | * during playback or setup. If no listener is specified, 560 | * or if the listener returned false, VideoView will inform 561 | * the user of any errors. 562 | * 563 | * @param l The callback that will be run 564 | */ 565 | public void setOnErrorListener(MediaPlayer.OnErrorListener l) 566 | { 567 | mOnErrorListener = l; 568 | } 569 | 570 | /** 571 | * Register a callback to be invoked when an informational event 572 | * occurs during playback or setup. 573 | * 574 | * @param l The callback that will be run 575 | */ 576 | public void setOnInfoListener(MediaPlayer.OnInfoListener l) { 577 | mOnInfoListener = l; 578 | } 579 | 580 | SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() 581 | { 582 | public void surfaceChanged(SurfaceHolder holder, int format, 583 | int w, int h) 584 | { 585 | mSurfaceWidth = w; 586 | mSurfaceHeight = h; 587 | boolean isValidState = (mTargetState == STATE_PLAYING); 588 | boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h); 589 | if (mMediaPlayer != null && isValidState && hasValidSize) { 590 | if (mSeekWhenPrepared != 0) { 591 | seekTo(mSeekWhenPrepared); 592 | } 593 | start(); 594 | } 595 | } 596 | 597 | public void surfaceCreated(SurfaceHolder holder) 598 | { 599 | mSurfaceHolder = holder; 600 | openVideo(); 601 | enableOrientationDetect(); 602 | } 603 | 604 | public void surfaceDestroyed(SurfaceHolder holder) 605 | { 606 | // after we return from this we can't use the surface any more 607 | mSurfaceHolder = null; 608 | if (mMediaController != null) mMediaController.hide(); 609 | release(true); 610 | disableOrientationDetect(); 611 | } 612 | }; 613 | 614 | private void enableOrientationDetect() { 615 | if (mAutoRotation && mOrientationDetector == null) { 616 | mOrientationDetector = new OrientationDetector(mContext); 617 | mOrientationDetector.setOrientationChangeListener(UniversalVideoView.this); 618 | mOrientationDetector.enable(); 619 | } 620 | } 621 | 622 | private void disableOrientationDetect() { 623 | if (mOrientationDetector != null) { 624 | mOrientationDetector.disable(); 625 | } 626 | } 627 | 628 | /* 629 | * release the media player in any state 630 | */ 631 | private void release(boolean cleartargetstate) { 632 | if (mMediaPlayer != null) { 633 | mMediaPlayer.reset(); 634 | mMediaPlayer.release(); 635 | mMediaPlayer = null; 636 | mCurrentState = STATE_IDLE; 637 | if (cleartargetstate) { 638 | mTargetState = STATE_IDLE; 639 | } 640 | } 641 | } 642 | 643 | @Override 644 | public boolean onTouchEvent(MotionEvent ev) { 645 | if (isInPlaybackState() && mMediaController != null) { 646 | toggleMediaControlsVisibility(); 647 | } 648 | return false; 649 | } 650 | 651 | @Override 652 | public boolean onTrackballEvent(MotionEvent ev) { 653 | if (isInPlaybackState() && mMediaController != null) { 654 | toggleMediaControlsVisibility(); 655 | } 656 | return false; 657 | } 658 | 659 | @Override 660 | public boolean onKeyDown(int keyCode, KeyEvent event) 661 | { 662 | boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && 663 | keyCode != KeyEvent.KEYCODE_VOLUME_UP && 664 | keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && 665 | keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && 666 | keyCode != KeyEvent.KEYCODE_MENU && 667 | keyCode != KeyEvent.KEYCODE_CALL && 668 | keyCode != KeyEvent.KEYCODE_ENDCALL; 669 | if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) { 670 | if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || 671 | keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { 672 | if (mMediaPlayer.isPlaying()) { 673 | pause(); 674 | mMediaController.show(); 675 | } else { 676 | start(); 677 | mMediaController.hide(); 678 | } 679 | return true; 680 | } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { 681 | if (!mMediaPlayer.isPlaying()) { 682 | start(); 683 | mMediaController.hide(); 684 | } 685 | return true; 686 | } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP 687 | || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { 688 | if (mMediaPlayer.isPlaying()) { 689 | pause(); 690 | mMediaController.show(); 691 | } 692 | return true; 693 | } else { 694 | toggleMediaControlsVisibility(); 695 | } 696 | } 697 | 698 | return super.onKeyDown(keyCode, event); 699 | } 700 | 701 | private void toggleMediaControlsVisibility() { 702 | if (mMediaController.isShowing()) { 703 | mMediaController.hide(); 704 | } else { 705 | mMediaController.show(); 706 | } 707 | } 708 | 709 | 710 | @Override 711 | public void start() { 712 | if (!mPreparedBeforeStart && mMediaController != null) { 713 | mMediaController.showLoading(); 714 | } 715 | 716 | if (isInPlaybackState()) { 717 | mMediaPlayer.start(); 718 | mCurrentState = STATE_PLAYING; 719 | if (this.videoViewCallback != null) { 720 | this.videoViewCallback.onStart(mMediaPlayer); 721 | } 722 | } 723 | mTargetState = STATE_PLAYING; 724 | } 725 | 726 | @Override 727 | public void pause() { 728 | if (isInPlaybackState()) { 729 | if (mMediaPlayer.isPlaying()) { 730 | mMediaPlayer.pause(); 731 | mCurrentState = STATE_PAUSED; 732 | if (this.videoViewCallback != null) { 733 | this.videoViewCallback.onPause(mMediaPlayer); 734 | } 735 | } 736 | } 737 | mTargetState = STATE_PAUSED; 738 | } 739 | 740 | public void suspend() { 741 | release(false); 742 | } 743 | 744 | public void resume() { 745 | openVideo(); 746 | } 747 | 748 | @Override 749 | public int getDuration() { 750 | if (isInPlaybackState()) { 751 | return mMediaPlayer.getDuration(); 752 | } 753 | 754 | return -1; 755 | } 756 | 757 | @Override 758 | public int getCurrentPosition() { 759 | if (isInPlaybackState()) { 760 | return mMediaPlayer.getCurrentPosition(); 761 | } 762 | return 0; 763 | } 764 | 765 | @Override 766 | public void seekTo(int msec) { 767 | if (isInPlaybackState()) { 768 | mMediaPlayer.seekTo(msec); 769 | mSeekWhenPrepared = 0; 770 | } else { 771 | mSeekWhenPrepared = msec; 772 | } 773 | } 774 | 775 | @Override 776 | public boolean isPlaying() { 777 | return isInPlaybackState() && mMediaPlayer.isPlaying(); 778 | } 779 | 780 | @Override 781 | public int getBufferPercentage() { 782 | if (mMediaPlayer != null) { 783 | return mCurrentBufferPercentage; 784 | } 785 | return 0; 786 | } 787 | 788 | private boolean isInPlaybackState() { 789 | return (mMediaPlayer != null && 790 | mCurrentState != STATE_ERROR && 791 | mCurrentState != STATE_IDLE && 792 | mCurrentState != STATE_PREPARING); 793 | } 794 | 795 | @Override 796 | public boolean canPause() { 797 | return mCanPause; 798 | } 799 | 800 | @Override 801 | public boolean canSeekBackward() { 802 | return mCanSeekBack; 803 | } 804 | 805 | @Override 806 | public boolean canSeekForward() { 807 | return mCanSeekForward; 808 | } 809 | 810 | @Override 811 | public void closePlayer() { 812 | release(true); 813 | } 814 | 815 | @Override 816 | public void setFullscreen(boolean fullscreen) { 817 | int screenOrientation = fullscreen ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE 818 | : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 819 | setFullscreen(fullscreen, screenOrientation); 820 | } 821 | 822 | @Override 823 | public void setFullscreen(boolean fullscreen, int screenOrientation) { 824 | // Activity需要设置为: android:configChanges="keyboardHidden|orientation|screenSize" 825 | Activity activity = (Activity) mContext; 826 | 827 | if (fullscreen) { 828 | if (mVideoViewLayoutWidth == 0 && mVideoViewLayoutHeight == 0) { 829 | ViewGroup.LayoutParams params = getLayoutParams(); 830 | mVideoViewLayoutWidth = params.width;//保存全屏之前的参数 831 | mVideoViewLayoutHeight = params.height; 832 | } 833 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 834 | activity.setRequestedOrientation(screenOrientation); 835 | } else { 836 | ViewGroup.LayoutParams params = getLayoutParams(); 837 | params.width = mVideoViewLayoutWidth;//使用全屏之前的参数 838 | params.height = mVideoViewLayoutHeight; 839 | setLayoutParams(params); 840 | 841 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 842 | activity.setRequestedOrientation(screenOrientation); 843 | } 844 | mMediaController.toggleButtons(fullscreen); 845 | if (videoViewCallback != null) { 846 | videoViewCallback.onScaleChange(fullscreen); 847 | } 848 | } 849 | 850 | /* 851 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 852 | private void switchTitleBar(boolean show) { 853 | if (mContext instanceof AppCompatActivity) { 854 | AppCompatActivity activity = (AppCompatActivity)mContext; 855 | android.support.v7.app.ActionBar supportActionBar = activity.getSupportActionBar(); 856 | if (supportActionBar != null) { 857 | if (show) { 858 | supportActionBar.show(); 859 | } else { 860 | supportActionBar.hide(); 861 | } 862 | } 863 | }else if (mContext instanceof Activity) { 864 | Activity activity = (Activity)mContext; 865 | if(activity.getActionBar() != null) { 866 | if (show) { 867 | activity.getActionBar().show(); 868 | } else { 869 | activity.getActionBar().hide(); 870 | } 871 | } 872 | } 873 | } 874 | */ 875 | 876 | 877 | public interface VideoViewCallback { 878 | void onScaleChange(boolean isFullscreen); 879 | void onPause(final MediaPlayer mediaPlayer); 880 | void onStart(final MediaPlayer mediaPlayer); 881 | void onBufferingStart(final MediaPlayer mediaPlayer); 882 | void onBufferingEnd(final MediaPlayer mediaPlayer); 883 | } 884 | 885 | public void setVideoViewCallback(VideoViewCallback callback) { 886 | this.videoViewCallback = callback; 887 | } 888 | } 889 | -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_back_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_back_btn.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_common_ic_loading_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_common_ic_loading_icon.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_itv_player_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_itv_player_play.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_on_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_on_error.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_play_vb_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_play_vb_bg.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_play_vb_bg_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_play_vb_bg_progress.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_player_player_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_player_player_btn.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_player_scale_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_player_scale_btn.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_seek_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_seek_dot.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_star_zoom_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_star_zoom_in.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_stop_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_stop_btn.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable-xhdpi/uvv_volume_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoview/src/main/res/drawable-xhdpi/uvv_volume_btn.png -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable/uvv_progress_rotate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /universalvideoview/src/main/res/drawable/uvv_star_play_progress_seek.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /universalvideoview/src/main/res/layout/uvv_on_error_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /universalvideoview/src/main/res/layout/uvv_on_loading_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 22 | -------------------------------------------------------------------------------- /universalvideoview/src/main/res/layout/uvv_player_controller.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 38 | 39 | 49 | 50 | 63 | 64 | 65 | 66 | 75 | 76 | 83 | 84 | 96 | 97 | 109 | 110 | 118 | 119 | 127 | 128 | 142 | 143 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /universalvideoview/src/main/res/values/color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #969696 5 | #ff000000 6 | #ffF0F0F0 7 | #7f000000 8 | 9 | -------------------------------------------------------------------------------- /universalvideoview/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /universalvideoview/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | UniversalVideoView Library 3 | 4 | Settings 5 | 6 | -------------------------------------------------------------------------------- /universalvideoview/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /universalvideoview/src/main/res/values/universal_videoview_attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /universalvideoviewsample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /universalvideoviewsample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.universalvideoviewsample" 9 | minSdkVersion 9 10 | targetSdkVersion 23 11 | versionCode 3 12 | versionName "1.0.2" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:23.0.0' 25 | compile project(':universalvideoview') 26 | } 27 | -------------------------------------------------------------------------------- /universalvideoviewsample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\AndroidDev\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /universalvideoviewsample/src/androidTest/java/com/universalvideoviewsample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.universalvideoviewsample; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /universalvideoviewsample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 18 | android:label="@string/app_name" > 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /universalvideoviewsample/src/main/java/com/universalvideoviewsample/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Andy Ke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | package com.universalvideoviewsample; 19 | 20 | import android.media.MediaPlayer; 21 | import android.support.v7.app.AppCompatActivity; 22 | import android.os.Bundle; 23 | import android.util.Log; 24 | import android.view.View; 25 | import android.view.ViewGroup; 26 | import android.widget.TextView; 27 | 28 | import com.universalvideoview.UniversalMediaController; 29 | import com.universalvideoview.UniversalVideoView; 30 | 31 | public class MainActivity extends AppCompatActivity implements UniversalVideoView.VideoViewCallback{ 32 | 33 | private static final String TAG = "MainActivity"; 34 | private static final String SEEK_POSITION_KEY = "SEEK_POSITION_KEY"; 35 | private static final String VIDEO_URL = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"; 36 | 37 | UniversalVideoView mVideoView; 38 | UniversalMediaController mMediaController; 39 | 40 | View mBottomLayout; 41 | View mVideoLayout; 42 | TextView mStart; 43 | 44 | private int mSeekPosition; 45 | private int cachedHeight; 46 | private boolean isFullscreen; 47 | 48 | @Override 49 | protected void onCreate(Bundle savedInstanceState) { 50 | super.onCreate(savedInstanceState); 51 | setContentView(R.layout.activity_main); 52 | 53 | mVideoLayout = findViewById(R.id.video_layout); 54 | mBottomLayout = findViewById(R.id.bottom_layout); 55 | mVideoView = (UniversalVideoView) findViewById(R.id.videoView); 56 | mMediaController = (UniversalMediaController) findViewById(R.id.media_controller); 57 | mVideoView.setMediaController(mMediaController); 58 | setVideoAreaSize(); 59 | mVideoView.setVideoViewCallback(this); 60 | mStart = (TextView) findViewById(R.id.start); 61 | 62 | mStart.setOnClickListener(new View.OnClickListener() { 63 | @Override 64 | public void onClick(View v) { 65 | if (mSeekPosition > 0) { 66 | mVideoView.seekTo(mSeekPosition); 67 | } 68 | mVideoView.start(); 69 | mMediaController.setTitle("Big Buck Bunny"); 70 | } 71 | }); 72 | 73 | mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 74 | @Override 75 | public void onCompletion(MediaPlayer mp) { 76 | Log.d(TAG, "onCompletion "); 77 | } 78 | }); 79 | 80 | } 81 | 82 | @Override 83 | protected void onPause() { 84 | super.onPause(); 85 | Log.d(TAG, "onPause "); 86 | if (mVideoView != null && mVideoView.isPlaying()) { 87 | mSeekPosition = mVideoView.getCurrentPosition(); 88 | Log.d(TAG, "onPause mSeekPosition=" + mSeekPosition); 89 | mVideoView.pause(); 90 | } 91 | } 92 | 93 | /** 94 | * 置视频区域大小 95 | */ 96 | private void setVideoAreaSize() { 97 | mVideoLayout.post(new Runnable() { 98 | @Override 99 | public void run() { 100 | int width = mVideoLayout.getWidth(); 101 | cachedHeight = (int) (width * 405f / 720f); 102 | // cachedHeight = (int) (width * 3f / 4f); 103 | // cachedHeight = (int) (width * 9f / 16f); 104 | ViewGroup.LayoutParams videoLayoutParams = mVideoLayout.getLayoutParams(); 105 | videoLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 106 | videoLayoutParams.height = cachedHeight; 107 | mVideoLayout.setLayoutParams(videoLayoutParams); 108 | mVideoView.setVideoPath(VIDEO_URL); 109 | mVideoView.requestFocus(); 110 | } 111 | }); 112 | } 113 | 114 | 115 | @Override 116 | protected void onSaveInstanceState(Bundle outState) { 117 | super.onSaveInstanceState(outState); 118 | Log.d(TAG, "onSaveInstanceState Position=" + mVideoView.getCurrentPosition()); 119 | outState.putInt(SEEK_POSITION_KEY, mSeekPosition); 120 | } 121 | 122 | @Override 123 | protected void onRestoreInstanceState(Bundle outState) { 124 | super.onRestoreInstanceState(outState); 125 | mSeekPosition = outState.getInt(SEEK_POSITION_KEY); 126 | Log.d(TAG, "onRestoreInstanceState Position=" + mSeekPosition); 127 | } 128 | 129 | 130 | @Override 131 | public void onScaleChange(boolean isFullscreen) { 132 | this.isFullscreen = isFullscreen; 133 | if (isFullscreen) { 134 | ViewGroup.LayoutParams layoutParams = mVideoLayout.getLayoutParams(); 135 | layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 136 | layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 137 | mVideoLayout.setLayoutParams(layoutParams); 138 | mBottomLayout.setVisibility(View.GONE); 139 | 140 | } else { 141 | ViewGroup.LayoutParams layoutParams = mVideoLayout.getLayoutParams(); 142 | layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 143 | layoutParams.height = this.cachedHeight; 144 | mVideoLayout.setLayoutParams(layoutParams); 145 | mBottomLayout.setVisibility(View.VISIBLE); 146 | } 147 | 148 | switchTitleBar(!isFullscreen); 149 | } 150 | 151 | private void switchTitleBar(boolean show) { 152 | android.support.v7.app.ActionBar supportActionBar = getSupportActionBar(); 153 | if (supportActionBar != null) { 154 | if (show) { 155 | supportActionBar.show(); 156 | } else { 157 | supportActionBar.hide(); 158 | } 159 | } 160 | } 161 | 162 | @Override 163 | public void onPause(MediaPlayer mediaPlayer) { 164 | Log.d(TAG, "onPause UniversalVideoView callback"); 165 | } 166 | 167 | @Override 168 | public void onStart(MediaPlayer mediaPlayer) { 169 | Log.d(TAG, "onStart UniversalVideoView callback"); 170 | } 171 | 172 | @Override 173 | public void onBufferingStart(MediaPlayer mediaPlayer) { 174 | Log.d(TAG, "onBufferingStart UniversalVideoView callback"); 175 | } 176 | 177 | @Override 178 | public void onBufferingEnd(MediaPlayer mediaPlayer) { 179 | Log.d(TAG, "onBufferingEnd UniversalVideoView callback"); 180 | } 181 | 182 | @Override 183 | public void onBackPressed() { 184 | if (this.isFullscreen) { 185 | mVideoView.setFullscreen(false); 186 | } else { 187 | super.onBackPressed(); 188 | } 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /universalvideoviewsample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 20 | 21 | 26 | 27 | 28 | 29 | 35 | 36 |