├── .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 | 
9 | 
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 |
44 |
45 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/universalvideoviewsample/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/universalvideoviewsample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoviewsample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/universalvideoviewsample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoviewsample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/universalvideoviewsample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoviewsample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/universalvideoviewsample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linsea/UniversalVideoView/a77a55f91bdc193a0548bf1472280558c28ddd97/universalvideoviewsample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/universalvideoviewsample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/universalvideoviewsample/src/main/res/values/color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #ff50b100
5 | #ff000000
6 | #ffF0F0F0
7 | #ffbbbbbb
8 |
9 |
--------------------------------------------------------------------------------
/universalvideoviewsample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/universalvideoviewsample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Universal VideoView Sample
3 |
4 | Hello world!
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/universalvideoviewsample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------