diff --git a/build.gradle b/build.gradle new file mode 100755 index 0000000..a098695 --- /dev/null +++ b/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.2.3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} + +ext { + compileSdkVersion = 22 + buildToolsVersion = "22.0.1" +} \ No newline at end of file diff --git a/build/intermediates/dex-cache/cache.xml b/build/intermediates/dex-cache/cache.xml new file mode 100644 index 0000000..fadb3b5 --- /dev/null +++ b/build/intermediates/dex-cache/cache.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties new file mode 100755 index 0000000..cb853f9 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,33 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +VERSION_NAME=0.3.1-dev001 +VERSION_CODE=301000 +GROUP=tv.danmaku.ijk.media + +POM_DESCRIPTION=Video player based on FFmpeg n2.7 +POM_URL=https://github.com/Bilibili/ijkplayer +POM_SCM_URL=https://github.com/Bilibili/ijkplayer +POM_SCM_CONNECTION=git@github.com:Bilibili/ijkplayer.git +POM_SCM_DEV_CONNECTION=git@github.com:Bilibili/ijkplayer.git +POM_LICENCE_NAME=LGPLv2.1 or later +POM_LICENCE_URL=https://www.gnu.org/licenses/lgpl-2.1.html +POM_LICENCE_DIST=repo +POM_DEVELOPER_ID=bbcallen +POM_DEVELOPER_NAME=Zhang Rui diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100755 index 0000000..8c0fb64 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100755 index 0000000..0c71e76 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100755 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/ijkplayer.iml b/ijkplayer.iml new file mode 100644 index 0000000..424405c --- /dev/null +++ b/ijkplayer.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/local.properties b/local.properties new file mode 100644 index 0000000..fc3d4fd --- /dev/null +++ b/local.properties @@ -0,0 +1,11 @@ +## This file is automatically generated by Android Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Sat Jan 16 14:58:21 CST 2016 +sdk.dir=/Users/zhangyang/Library/Android/sdk diff --git a/player-java/.gitignore b/player-java/.gitignore new file mode 100755 index 0000000..796b96d --- /dev/null +++ b/player-java/.gitignore @@ -0,0 +1 @@ +/build diff --git a/player-java/build.gradle b/player-java/build.gradle new file mode 100755 index 0000000..1e4fadb --- /dev/null +++ b/player-java/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'com.android.library' + +android { + // http://tools.android.com/tech-docs/new-build-system/tips + //noinspection GroovyAssignabilityCheck + compileSdkVersion rootProject.ext.compileSdkVersion + //noinspection GroovyAssignabilityCheck + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion 9 + targetSdkVersion 22 + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} + +apply from: '../tools/gradle-mvn-push.gradle' \ No newline at end of file diff --git a/player-java/gradle.properties b/player-java/gradle.properties new file mode 100755 index 0000000..a6b3c5c --- /dev/null +++ b/player-java/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=ijkplayer-java +POM_ARTIFACT_ID=ijkplayer-java +POM_PACKAGING=aar \ No newline at end of file diff --git a/player-java/player-java.iml b/player-java/player-java.iml new file mode 100644 index 0000000..59e3407 --- /dev/null +++ b/player-java/player-java.iml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/player-java/proguard-rules.pro b/player-java/proguard-rules.pro new file mode 100755 index 0000000..034485d --- /dev/null +++ b/player-java/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /opt/android/ADK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/player-java/src/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java b/player-java/src/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java new file mode 100755 index 0000000..60d1d73 --- /dev/null +++ b/player-java/src/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java @@ -0,0 +1,13 @@ +package tv.danmaku.ijk.media.player; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/player-java/src/main/.classpath b/player-java/src/main/.classpath new file mode 100755 index 0000000..b3caa8c --- /dev/null +++ b/player-java/src/main/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/player-java/src/main/.project b/player-java/src/main/.project new file mode 100755 index 0000000..31663eb --- /dev/null +++ b/player-java/src/main/.project @@ -0,0 +1,33 @@ + + + ijkplayer-java + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/player-java/src/main/.settings/org.eclipse.jdt.core.prefs b/player-java/src/main/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 0000000..b080d2d --- /dev/null +++ b/player-java/src/main/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/player-java/src/main/AndroidManifest.xml b/player-java/src/main/AndroidManifest.xml new file mode 100755 index 0000000..c87fe47 --- /dev/null +++ b/player-java/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java new file mode 100755 index 0000000..ab332be --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * Copyright (C) 2013 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player; + +import java.io.IOException; +import java.lang.ref.WeakReference; + +import tv.danmaku.ijk.media.player.pragma.DebugLog; + +import android.annotation.TargetApi; +import android.content.Context; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Build; +import android.text.TextUtils; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceHolder.Callback; + +public class AndroidMediaPlayer extends SimpleMediaPlayer { + private MediaPlayer mInternalMediaPlayer; + private AndroidMediaPlayerListenerHolder mInternalListenerAdapter; + private String mDataSource; + + private Object mInitLock = new Object(); + private boolean mIsReleased; + + private boolean mKeepInBackground; + + private static MediaInfo sMediaInfo; + + public AndroidMediaPlayer() { + synchronized (mInitLock) { + mInternalMediaPlayer = new MediaPlayer(); + } + mInternalMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mInternalListenerAdapter = new AndroidMediaPlayerListenerHolder(this); + attachInternalListeners(); + } + + @Override + public void setDisplay(SurfaceHolder sh) { + synchronized (mInitLock) { + if (!mIsReleased) { + mInternalMediaPlayer.setDisplay(sh); + if (sh != null) + sh.addCallback(mSurfaceCallback); + } + } + } + + private SurfaceHolder.Callback mSurfaceCallback = new Callback() { + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + } + + public void surfaceCreated(SurfaceHolder holder) { + } + + public void surfaceDestroyed(SurfaceHolder holder) { + if (mInternalMediaPlayer != null) { + if (!mKeepInBackground) { + mInternalMediaPlayer.release(); + } + } + } + }; + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void setSurface(Surface surface) { + mInternalMediaPlayer.setSurface(surface); + } + + @Override + public void setDataSource(String path) throws IOException, + IllegalArgumentException, SecurityException, IllegalStateException { + mDataSource = path; + + Uri uri = Uri.parse(path); + String scheme = uri.getScheme(); + if (!TextUtils.isEmpty(scheme) && scheme.equalsIgnoreCase("file")) { + mInternalMediaPlayer.setDataSource(uri.getPath()); + } else { + mInternalMediaPlayer.setDataSource(path); + } + } + + @Override + public String getDataSource() { + return mDataSource; + } + + @Override + public void prepareAsync() throws IllegalStateException { + mInternalMediaPlayer.prepareAsync(); + } + + @Override + public void start() throws IllegalStateException { + mInternalMediaPlayer.start(); + } + + @Override + public void stop() throws IllegalStateException { + mInternalMediaPlayer.stop(); + } + + @Override + public void pause() throws IllegalStateException { + mInternalMediaPlayer.pause(); + } + + @Override + public void setScreenOnWhilePlaying(boolean screenOn) { + mInternalMediaPlayer.setScreenOnWhilePlaying(screenOn); + } + + @Override + public int getVideoWidth() { + return mInternalMediaPlayer.getVideoWidth(); + } + + @Override + public int getVideoHeight() { + return mInternalMediaPlayer.getVideoHeight(); + } + + @Override + public int getVideoSarNum() { + return 1; + } + + @Override + public int getVideoSarDen() { + return 1; + } + + @Override + public boolean isPlaying() { + try { + return mInternalMediaPlayer.isPlaying(); + } catch (IllegalStateException e) { + DebugLog.printStackTrace(e); + return false; + } + } + + @Override + public void seekTo(long msec) throws IllegalStateException { + mInternalMediaPlayer.seekTo((int) msec); + } + + @Override + public long getCurrentPosition() { + try { + return mInternalMediaPlayer.getCurrentPosition(); + } catch (IllegalStateException e) { + DebugLog.printStackTrace(e); + return 0; + } + } + + @Override + public long getDuration() { + try { + return mInternalMediaPlayer.getDuration(); + } catch (IllegalStateException e) { + DebugLog.printStackTrace(e); + return 0; + } + } + + @Override + public void release() { + mIsReleased = true; + mInternalMediaPlayer.release(); + + resetListeners(); + attachInternalListeners(); + } + + @Override + public void reset() { + mInternalMediaPlayer.reset(); + + resetListeners(); + attachInternalListeners(); + } + + @Override + public void setVolume(float leftVolume, float rightVolume) { + mInternalMediaPlayer.setVolume(leftVolume, rightVolume); + } + + @Override + public MediaInfo getMediaInfo() { + if (sMediaInfo == null) { + MediaInfo module = new MediaInfo(); + + module.mVideoDecoder = "android"; + module.mVideoDecoderImpl = "HW"; + + module.mAudioDecoder = "android"; + module.mAudioDecoderImpl = "HW"; + + sMediaInfo = module; + } + + return sMediaInfo; + } + + /*-------------------- + * misc + */ + @Override + public void setWakeMode(Context context, int mode) { + mInternalMediaPlayer.setWakeMode(context, mode); + } + + @Override + public void setAudioStreamType(int streamtype) { + mInternalMediaPlayer.setAudioStreamType(streamtype); + } + + @Override + public void setKeepInBackground(boolean keepInBackground) { + mKeepInBackground = keepInBackground; + } + + /*-------------------- + * Listeners adapter + */ + private void attachInternalListeners() { + mInternalMediaPlayer.setOnPreparedListener(mInternalListenerAdapter); + mInternalMediaPlayer + .setOnBufferingUpdateListener(mInternalListenerAdapter); + mInternalMediaPlayer.setOnCompletionListener(mInternalListenerAdapter); + mInternalMediaPlayer + .setOnSeekCompleteListener(mInternalListenerAdapter); + mInternalMediaPlayer + .setOnVideoSizeChangedListener(mInternalListenerAdapter); + mInternalMediaPlayer.setOnErrorListener(mInternalListenerAdapter); + mInternalMediaPlayer.setOnInfoListener(mInternalListenerAdapter); + } + + private class AndroidMediaPlayerListenerHolder implements + MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, + MediaPlayer.OnBufferingUpdateListener, + MediaPlayer.OnSeekCompleteListener, + MediaPlayer.OnVideoSizeChangedListener, + MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener { + public WeakReference mWeakMediaPlayer; + + public AndroidMediaPlayerListenerHolder(AndroidMediaPlayer mp) { + mWeakMediaPlayer = new WeakReference(mp); + } + + @Override + public boolean onInfo(MediaPlayer mp, int what, int extra) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return false; + + return notifyOnInfo(what, extra); + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return false; + + return notifyOnError(what, extra); + } + + @Override + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return; + + notifyOnVideoSizeChanged(width, height, 1, 1); + } + + @Override + public void onSeekComplete(MediaPlayer mp) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return; + + notifyOnSeekComplete(); + } + + @Override + public void onBufferingUpdate(MediaPlayer mp, int percent) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return; + + notifyOnBufferingUpdate(percent); + } + + @Override + public void onCompletion(MediaPlayer mp) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return; + + notifyOnCompletion(); + } + + @Override + public void onPrepared(MediaPlayer mp) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return; + + notifyOnPrepared(); + } + } +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/BaseMediaPlayer.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/BaseMediaPlayer.java new file mode 100755 index 0000000..f17772d --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/BaseMediaPlayer.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013-2014 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.view.Surface; + +/** + * @author bbcallen + * + * Optional interface default implements + */ +public abstract class BaseMediaPlayer implements IMediaPlayer { + private boolean mIsLogEnabled; + + public boolean isLogEnabled() { + return mIsLogEnabled; + } + + @Override + public void setLogEnabled(boolean enable) { + mIsLogEnabled = enable; + } + + @Override + public boolean isPlayable() { + return true; + } + + @Override + public void setAudioStreamType(int streamtype) { + } + + @Override + public void setKeepInBackground(boolean keepInBackground) { + } + + @Override + public int getVideoSarNum() { + return 1; + } + + @Override + public int getVideoSarDen() { + return 1; + } + + @Deprecated + @Override + public void setWakeMode(Context context, int mode) { + return; + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void setSurface(Surface surface) { + } +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/IMediaPlayer.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/IMediaPlayer.java new file mode 100755 index 0000000..fc8ab9e --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/IMediaPlayer.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2013-2014 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player; + +import java.io.IOException; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.view.Surface; +import android.view.SurfaceHolder; + +public interface IMediaPlayer { + /* + * Do not change these values without updating their counterparts in native + */ + public static final int MEDIA_INFO_UNKNOWN = 1; + public static final int MEDIA_INFO_STARTED_AS_NEXT = 2; + public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; + public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; + public static final int MEDIA_INFO_BUFFERING_START = 701; + public static final int MEDIA_INFO_BUFFERING_END = 702; + public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; + public static final int MEDIA_INFO_NOT_SEEKABLE = 801; + public static final int MEDIA_INFO_METADATA_UPDATE = 802; + public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900; + + public static final int MEDIA_ERROR_UNKNOWN = 1; + public static final int MEDIA_ERROR_SERVER_DIED = 100; + public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; + public static final int MEDIA_ERROR_IO = -1004; + public static final int MEDIA_ERROR_MALFORMED = -1007; + public static final int MEDIA_ERROR_UNSUPPORTED = -1010; + public static final int MEDIA_ERROR_TIMED_OUT = -110; + + public abstract void setDisplay(SurfaceHolder sh); + + public abstract void setDataSource(String path) throws IOException, + IllegalArgumentException, SecurityException, IllegalStateException; + + public abstract String getDataSource(); + + public abstract void prepareAsync() throws IllegalStateException; + + public abstract void start() throws IllegalStateException; + + public abstract void stop() throws IllegalStateException; + + public abstract void pause() throws IllegalStateException; + + public abstract void setScreenOnWhilePlaying(boolean screenOn); + + public abstract int getVideoWidth(); + + public abstract int getVideoHeight(); + + public abstract boolean isPlaying(); + + public abstract void seekTo(long msec) throws IllegalStateException; + + public abstract long getCurrentPosition(); + + public abstract long getDuration(); + + public abstract void release(); + + public abstract void reset(); + + public abstract void setVolume(float leftVolume, float rightVolume); + + public abstract MediaInfo getMediaInfo(); + + public abstract void setLogEnabled(boolean enable); + + public abstract boolean isPlayable(); + + public abstract void setOnPreparedListener(OnPreparedListener listener); + + public abstract void setOnCompletionListener(OnCompletionListener listener); + + public abstract void setOnBufferingUpdateListener( + OnBufferingUpdateListener listener); + + public abstract void setOnSeekCompleteListener( + OnSeekCompleteListener listener); + + public abstract void setOnVideoSizeChangedListener( + OnVideoSizeChangedListener listener); + + public abstract void setOnErrorListener(OnErrorListener listener); + + public abstract void setOnInfoListener(OnInfoListener listener); + + /*-------------------- + * Listeners + */ + public static interface OnPreparedListener { + public void onPrepared(IMediaPlayer mp); + } + + public static interface OnCompletionListener { + public void onCompletion(IMediaPlayer mp); + } + + public static interface OnBufferingUpdateListener { + public void onBufferingUpdate(IMediaPlayer mp, int percent); + } + + public static interface OnSeekCompleteListener { + public void onSeekComplete(IMediaPlayer mp); + } + + public static interface OnVideoSizeChangedListener { + public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, + int sar_num, int sar_den); + } + + public static interface OnErrorListener { + public boolean onError(IMediaPlayer mp, int what, int extra); + } + + public static interface OnInfoListener { + public boolean onInfo(IMediaPlayer mp, int what, int extra); + } + + /*-------------------- + * Optional + */ + public abstract void setAudioStreamType(int streamtype); + + public abstract void setKeepInBackground(boolean keepInBackground); + + public abstract int getVideoSarNum(); + + public abstract int getVideoSarDen(); + + @Deprecated + public abstract void setWakeMode(Context context, int mode); + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + public abstract void setSurface(Surface surface); +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkLibLoader.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkLibLoader.java new file mode 100755 index 0000000..a252092 --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkLibLoader.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2013-2014 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player; + +public interface IjkLibLoader { + public void loadLibrary(String libName) throws UnsatisfiedLinkError, + SecurityException; +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaCodecInfo.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaCodecInfo.java new file mode 100755 index 0000000..3e24902 --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaCodecInfo.java @@ -0,0 +1,283 @@ +package tv.danmaku.ijk.media.player; + +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; + +import android.annotation.TargetApi; +import android.media.MediaCodecInfo; +import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaCodecInfo.CodecProfileLevel; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; + +public class IjkMediaCodecInfo { + private final static String TAG = "IjkMediaCodecInfo"; + + public static int RANK_MAX = 1000; + public static int RANK_TESTED = 800; + public static int RANK_ACCEPTABLE = 700; + public static int RANK_LAST_CHANCE = 600; + public static int RANK_SECURE = 300; + public static int RANK_SOFTWARE = 200; + public static int RANK_NON_STANDARD = 100; + public static int RANK_NO_SENSE = 0; + + public MediaCodecInfo mCodecInfo; + public int mRank = 0; + public String mMimeType; + + private static Map sKnownCodecList; + + private static synchronized Map getKnownCodecList() { + if (sKnownCodecList != null) + return sKnownCodecList; + + sKnownCodecList = new TreeMap( + String.CASE_INSENSITIVE_ORDER); + + // ----- Nvidia ----- + // Tegra3 + // Nexus 7 (2012) + // Tegra K1 + // Nexus 9 + sKnownCodecList.put("OMX.Nvidia.h264.decode", RANK_TESTED); + sKnownCodecList.put("OMX.Nvidia.h264.decode.secure", RANK_SECURE); + + // ----- Intel ----- + // Atom Z3735 + // Teclast X98 Air + sKnownCodecList.put("OMX.Intel.hw_vd.h264", RANK_TESTED + 1); + // Atom Z2560 + // Dell Venue 7 3730 + sKnownCodecList.put("OMX.Intel.VideoDecoder.AVC", RANK_TESTED); + + // ----- Qualcomm ----- + // MSM8260 + // Xiaomi MI 1S + sKnownCodecList.put("OMX.qcom.video.decoder.avc", RANK_TESTED); + sKnownCodecList.put("OMX.ittiam.video.decoder.avc", RANK_NO_SENSE); + + // ----- Samsung ----- + // Exynos 3110 + // Nexus S + sKnownCodecList.put("OMX.SEC.avc.dec", RANK_TESTED); + sKnownCodecList.put("OMX.SEC.AVC.Decoder", RANK_TESTED - 1); + // OMX.SEC.avcdec doesn't reorder output pictures on GT-9100 + sKnownCodecList.put("OMX.SEC.avcdec", RANK_TESTED - 2); + sKnownCodecList.put("OMX.SEC.avc.sw.dec", RANK_SOFTWARE); + // Exynos 5 ? + sKnownCodecList.put("OMX.Exynos.avc.dec", RANK_TESTED); + sKnownCodecList.put("OMX.Exynos.AVC.Decoder", RANK_TESTED - 1); + + // ------ Huawei hisilicon ------ + // Kirin 910, Mali 450 MP + // Huawei HONOR 3C (H30-L01) + sKnownCodecList.put("OMX.k3.video.decoder.avc", RANK_TESTED); + // Kirin 920, Mali T624 + // Huawei HONOR 6 + sKnownCodecList.put("OMX.IMG.MSVDX.Decoder.AVC", RANK_TESTED); + + // ----- TI ----- + // TI OMAP4460 + // Galaxy Nexus + sKnownCodecList.put("OMX.TI.DUCATI1.VIDEO.DECODER", RANK_TESTED); + + // ------ RockChip ------ + // Youku TVBox + sKnownCodecList.put("OMX.rk.video_decoder.avc", RANK_TESTED); + + // ------ AMLogic ----- + // MiBox1, 1s, 2 + sKnownCodecList.put("OMX.amlogic.avc.decoder.awesome", RANK_TESTED); + + // ------ Marvell ------ + // Lenovo A788t + sKnownCodecList.put("OMX.MARVELL.VIDEO.HW.CODA7542DECODER", RANK_TESTED); + sKnownCodecList.put("OMX.MARVELL.VIDEO.H264DECODER", RANK_SOFTWARE); + + // ----- TODO: need test ----- + sKnownCodecList.remove("OMX.BRCM.vc4.decoder.avc"); + sKnownCodecList.remove("OMX.brcm.video.h264.hw.decoder"); + sKnownCodecList.remove("OMX.brcm.video.h264.decoder"); + sKnownCodecList.remove("OMX.ST.VFM.H264Dec"); + sKnownCodecList.remove("OMX.allwinner.video.decoder.avc"); + sKnownCodecList.remove("OMX.MS.AVC.Decoder"); + sKnownCodecList.remove("OMX.hantro.81x0.video.decoder"); + sKnownCodecList.remove("OMX.hisi.video.decoder"); + sKnownCodecList.remove("OMX.cosmo.video.decoder.avc"); + sKnownCodecList.remove("OMX.duos.h264.decoder"); + + // Really ? + sKnownCodecList.remove("OMX.bluestacks.hw.decoder"); + + // --------------- + // Useless codec + // ----- google ----- + sKnownCodecList.put("OMX.google.h264.decoder", RANK_SOFTWARE); + sKnownCodecList.put("OMX.google.h264.lc.decoder", RANK_SOFTWARE); + // ----- huawei k920 ----- + sKnownCodecList.put("OMX.k3.ffmpeg.decoder", RANK_SOFTWARE); + sKnownCodecList.put("OMX.ffmpeg.video.decoder", RANK_SOFTWARE); + + return sKnownCodecList; + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public static IjkMediaCodecInfo setupCandidate(MediaCodecInfo codecInfo, + String mimeType) { + if (codecInfo == null + || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + return null; + + String name = codecInfo.getName(); + if (TextUtils.isEmpty(name)) + return null; + + name = name.toLowerCase(Locale.US); + int rank = RANK_NO_SENSE; + if (!name.startsWith("omx.")) { + rank = RANK_NON_STANDARD; + } else if (name.startsWith("omx.pv")) { + rank = RANK_SOFTWARE; + } else if (name.startsWith("omx.google.")) { + rank = RANK_SOFTWARE; + } else if (name.startsWith("omx.ffmpeg.")) { + rank = RANK_SOFTWARE; + } else if (name.startsWith("omx.k3.ffmpeg.")) { + rank = RANK_SOFTWARE; + } else if (name.startsWith("omx.avcodec.")) { + rank = RANK_SOFTWARE; + } else if (name.startsWith("omx.ittiam.")) { + // unknown codec in qualcomm SoC + rank = RANK_NO_SENSE; + } else if (name.startsWith("omx.mtk.")) { + // 1. MTK only works on 4.3 and above + // 2. MTK works on MIUI 6 (4.2.1) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) + rank = RANK_NO_SENSE; + else + rank = RANK_TESTED; + } else { + Integer knownRank = getKnownCodecList().get(name); + if (knownRank != null) { + rank = knownRank; + } else { + try { + CodecCapabilities cap = codecInfo + .getCapabilitiesForType(mimeType); + if (cap != null) + rank = RANK_ACCEPTABLE; + else + rank = RANK_LAST_CHANCE; + } catch (Throwable e) { + rank = RANK_LAST_CHANCE; + } + } + } + + IjkMediaCodecInfo candidate = new IjkMediaCodecInfo(); + candidate.mCodecInfo = codecInfo; + candidate.mRank = rank; + candidate.mMimeType = mimeType; + return candidate; + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public void dumpProfileLevels(String mimeType) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + return; + + try { + CodecCapabilities caps = mCodecInfo + .getCapabilitiesForType(mimeType); + int maxProfile = 0; + int maxLevel = 0; + if (caps != null) { + if (caps.profileLevels != null) { + for (CodecProfileLevel profileLevel : caps.profileLevels) { + if (profileLevel == null) + continue; + + maxProfile = Math.max(maxProfile, profileLevel.profile); + maxLevel = Math.max(maxLevel, profileLevel.level); + } + } + } + + Log.i(TAG, + String.format(Locale.US, "%s", + getProfileLevelName(maxProfile, maxLevel))); + } catch (Throwable e) { + Log.i(TAG, "profile-level: exception"); + } + } + + public static String getProfileLevelName(int profile, int level) { + return String.format(Locale.US, " %s Profile Level %s (%d,%d)", + getProfileName(profile), getLevelName(level), profile, level); + } + + public static String getProfileName(int profile) { + switch (profile) { + case CodecProfileLevel.AVCProfileBaseline: + return "Baseline"; + case CodecProfileLevel.AVCProfileMain: + return "Main"; + case CodecProfileLevel.AVCProfileExtended: + return "Extends"; + case CodecProfileLevel.AVCProfileHigh: + return "High"; + case CodecProfileLevel.AVCProfileHigh10: + return "High10"; + case CodecProfileLevel.AVCProfileHigh422: + return "High422"; + case CodecProfileLevel.AVCProfileHigh444: + return "High444"; + default: + return "Unknown"; + } + } + + public static String getLevelName(int level) { + switch (level) { + case CodecProfileLevel.AVCLevel1: + return "1"; + case CodecProfileLevel.AVCLevel1b: + return "1b"; + case CodecProfileLevel.AVCLevel11: + return "11"; + case CodecProfileLevel.AVCLevel12: + return "12"; + case CodecProfileLevel.AVCLevel13: + return "13"; + case CodecProfileLevel.AVCLevel2: + return "2"; + case CodecProfileLevel.AVCLevel21: + return "21"; + case CodecProfileLevel.AVCLevel22: + return "22"; + case CodecProfileLevel.AVCLevel3: + return "3"; + case CodecProfileLevel.AVCLevel31: + return "31"; + case CodecProfileLevel.AVCLevel32: + return "32"; + case CodecProfileLevel.AVCLevel4: + return "4"; + case CodecProfileLevel.AVCLevel41: + return "41"; + case CodecProfileLevel.AVCLevel42: + return "42"; + case CodecProfileLevel.AVCLevel5: + return "5"; + case CodecProfileLevel.AVCLevel51: + return "51"; + case 65536: // CodecProfileLevel.AVCLevel52: + return "52"; + default: + return "0"; + } + } +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaMeta.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaMeta.java new file mode 100755 index 0000000..cdd2002 --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaMeta.java @@ -0,0 +1,365 @@ +package tv.danmaku.ijk.media.player; + +import java.util.ArrayList; +import java.util.Locale; + +import android.os.Bundle; +import android.text.TextUtils; + +public class IjkMediaMeta { + // media meta + public static final String IJKM_KEY_FORMAT = "format"; + public static final String IJKM_KEY_DURATION_US = "duration_us"; + public static final String IJKM_KEY_START_US = "start_us"; + public static final String IJKM_KEY_BITRATE = "bitrate"; + public static final String IJKM_KEY_VIDEO_STREAM = "video"; + public static final String IJKM_KEY_AUDIO_STREAM = "audio"; + + // stream meta + public static final String IJKM_KEY_TYPE = "type"; + public static final String IJKM_VAL_TYPE__VIDEO = "video"; + public static final String IJKM_VAL_TYPE__AUDIO = "audio"; + public static final String IJKM_VAL_TYPE__UNKNOWN = "unknown"; + + public static final String IJKM_KEY_CODEC_NAME = "codec_name"; + public static final String IJKM_KEY_CODEC_PROFILE = "codec_profile"; + public static final String IJKM_KEY_CODEC_LONG_NAME = "codec_long_name"; + + // stream: video + public static final String IJKM_KEY_WIDTH = "width"; + public static final String IJKM_KEY_HEIGHT = "height"; + public static final String IJKM_KEY_FPS_NUM = "fps_num"; + public static final String IJKM_KEY_FPS_DEN = "fps_den"; + public static final String IJKM_KEY_TBR_NUM = "tbr_num"; + public static final String IJKM_KEY_TBR_DEN = "tbr_den"; + public static final String IJKM_KEY_SAR_NUM = "sar_num"; + public static final String IJKM_KEY_SAR_DEN = "sar_den"; + // stream: audio + public static final String IJKM_KEY_SAMPLE_RATE = "sample_rate"; + public static final String IJKM_KEY_CHANNEL_LAYOUT = "channel_layout"; + + public static final String IJKM_KEY_STREAMS = "streams"; + + public static final long AV_CH_FRONT_LEFT = 0x00000001; + public static final long AV_CH_FRONT_RIGHT = 0x00000002; + public static final long AV_CH_FRONT_CENTER = 0x00000004; + public static final long AV_CH_LOW_FREQUENCY = 0x00000008; + public static final long AV_CH_BACK_LEFT = 0x00000010; + public static final long AV_CH_BACK_RIGHT = 0x00000020; + public static final long AV_CH_FRONT_LEFT_OF_CENTER = 0x00000040; + public static final long AV_CH_FRONT_RIGHT_OF_CENTER = 0x00000080; + public static final long AV_CH_BACK_CENTER = 0x00000100; + public static final long AV_CH_SIDE_LEFT = 0x00000200; + public static final long AV_CH_SIDE_RIGHT = 0x00000400; + public static final long AV_CH_TOP_CENTER = 0x00000800; + public static final long AV_CH_TOP_FRONT_LEFT = 0x00001000; + public static final long AV_CH_TOP_FRONT_CENTER = 0x00002000; + public static final long AV_CH_TOP_FRONT_RIGHT = 0x00004000; + public static final long AV_CH_TOP_BACK_LEFT = 0x00008000; + public static final long AV_CH_TOP_BACK_CENTER = 0x00010000; + public static final long AV_CH_TOP_BACK_RIGHT = 0x00020000; + public static final long AV_CH_STEREO_LEFT = 0x20000000; + public static final long AV_CH_STEREO_RIGHT = 0x40000000; + public static final long AV_CH_WIDE_LEFT = 0x0000000080000000L; + public static final long AV_CH_WIDE_RIGHT = 0x0000000100000000L; + public static final long AV_CH_SURROUND_DIRECT_LEFT = 0x0000000200000000L; + public static final long AV_CH_SURROUND_DIRECT_RIGHT = 0x0000000400000000L; + public static final long AV_CH_LOW_FREQUENCY_2 = 0x0000000800000000L; + + public static final long AV_CH_LAYOUT_MONO = (AV_CH_FRONT_CENTER); + public static final long AV_CH_LAYOUT_STEREO = (AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT); + public static final long AV_CH_LAYOUT_2POINT1 = (AV_CH_LAYOUT_STEREO | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_2_1 = (AV_CH_LAYOUT_STEREO | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_SURROUND = (AV_CH_LAYOUT_STEREO | AV_CH_FRONT_CENTER); + public static final long AV_CH_LAYOUT_3POINT1 = (AV_CH_LAYOUT_SURROUND | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_4POINT0 = (AV_CH_LAYOUT_SURROUND | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_4POINT1 = (AV_CH_LAYOUT_4POINT0 | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_2_2 = (AV_CH_LAYOUT_STEREO + | AV_CH_SIDE_LEFT | AV_CH_SIDE_RIGHT); + public static final long AV_CH_LAYOUT_QUAD = (AV_CH_LAYOUT_STEREO + | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT); + public static final long AV_CH_LAYOUT_5POINT0 = (AV_CH_LAYOUT_SURROUND + | AV_CH_SIDE_LEFT | AV_CH_SIDE_RIGHT); + public static final long AV_CH_LAYOUT_5POINT1 = (AV_CH_LAYOUT_5POINT0 | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_5POINT0_BACK = (AV_CH_LAYOUT_SURROUND + | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT); + public static final long AV_CH_LAYOUT_5POINT1_BACK = (AV_CH_LAYOUT_5POINT0_BACK | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_6POINT0 = (AV_CH_LAYOUT_5POINT0 | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_6POINT0_FRONT = (AV_CH_LAYOUT_2_2 + | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER); + public static final long AV_CH_LAYOUT_HEXAGONAL = (AV_CH_LAYOUT_5POINT0_BACK | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_6POINT1 = (AV_CH_LAYOUT_5POINT1 | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_6POINT1_BACK = (AV_CH_LAYOUT_5POINT1_BACK | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_6POINT1_FRONT = (AV_CH_LAYOUT_6POINT0_FRONT | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_7POINT0 = (AV_CH_LAYOUT_5POINT0 + | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT); + public static final long AV_CH_LAYOUT_7POINT0_FRONT = (AV_CH_LAYOUT_5POINT0 + | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER); + public static final long AV_CH_LAYOUT_7POINT1 = (AV_CH_LAYOUT_5POINT1 + | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT); + public static final long AV_CH_LAYOUT_7POINT1_WIDE = (AV_CH_LAYOUT_5POINT1 + | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER); + public static final long AV_CH_LAYOUT_7POINT1_WIDE_BACK = (AV_CH_LAYOUT_5POINT1_BACK + | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER); + public static final long AV_CH_LAYOUT_OCTAGONAL = (AV_CH_LAYOUT_5POINT0 + | AV_CH_BACK_LEFT | AV_CH_BACK_CENTER | AV_CH_BACK_RIGHT); + public static final long AV_CH_LAYOUT_STEREO_DOWNMIX = (AV_CH_STEREO_LEFT | AV_CH_STEREO_RIGHT); + + public Bundle mMediaMeta; + + public String mFormat; + public long mDurationUS; + public long mStartUS; + public long mBitrate; + + public ArrayList mStreams; + public IjkStreamMeta mVideoStream; + public IjkStreamMeta mAudioStream; + + public String getString(String key) { + return mMediaMeta.getString(key); + } + + public int getInt(String key) { + return getInt(key, 0); + } + + public int getInt(String key, int defaultValue) { + String value = getString(key); + if (TextUtils.isEmpty(value)) + return defaultValue; + + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public long getLong(String key) { + return getLong(key, 0); + } + + public long getLong(String key, long defaultValue) { + String value = getString(key); + if (TextUtils.isEmpty(value)) + return defaultValue; + + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public ArrayList getParcelableArrayList(String key) { + return mMediaMeta.getParcelableArrayList(key); + } + + public String getDurationInline() { + long duration = mDurationUS + 5000; + long secs = duration / 1000000; + long mins = secs / 60; + secs %= 60; + long hours = mins / 60; + mins %= 60; + return String.format(Locale.US, "%02d:%02d:%02d", hours, mins, secs); + } + + public static IjkMediaMeta parse(Bundle mediaMeta) { + if (mediaMeta == null) + return null; + + IjkMediaMeta meta = new IjkMediaMeta(); + meta.mMediaMeta = mediaMeta; + + meta.mFormat = meta.getString(IJKM_KEY_FORMAT); + meta.mDurationUS = meta.getLong(IJKM_KEY_DURATION_US); + meta.mStartUS = meta.getLong(IJKM_KEY_START_US); + meta.mBitrate = meta.getLong(IJKM_KEY_BITRATE); + + int videoStreamIndex = meta.getInt(IJKM_KEY_VIDEO_STREAM, -1); + int audioStreamIndex = meta.getInt(IJKM_KEY_AUDIO_STREAM, -1); + + ArrayList streams = meta + .getParcelableArrayList(IJKM_KEY_STREAMS); + if (streams == null) + return meta; + + int index = -1; + for (Bundle streamBundle : streams) { + index++; + + if (streamBundle == null) { + continue; + } + + IjkStreamMeta streamMeta = new IjkStreamMeta(index); + streamMeta.mMeta = streamBundle; + streamMeta.mType = streamMeta.getString(IJKM_KEY_TYPE); + if (TextUtils.isEmpty(streamMeta.mType)) + continue; + + streamMeta.mCodecName = streamMeta.getString(IJKM_KEY_CODEC_NAME); + streamMeta.mCodecProfile = streamMeta + .getString(IJKM_KEY_CODEC_PROFILE); + streamMeta.mCodecLongName = streamMeta + .getString(IJKM_KEY_CODEC_LONG_NAME); + streamMeta.mBitrate = streamMeta.getInt(IJKM_KEY_BITRATE); + + if (streamMeta.mType.equalsIgnoreCase(IJKM_VAL_TYPE__VIDEO)) { + streamMeta.mWidth = streamMeta.getInt(IJKM_KEY_WIDTH); + streamMeta.mHeight = streamMeta.getInt(IJKM_KEY_HEIGHT); + streamMeta.mFpsNum = streamMeta.getInt(IJKM_KEY_FPS_NUM); + streamMeta.mFpsDen = streamMeta.getInt(IJKM_KEY_FPS_DEN); + streamMeta.mTbrNum = streamMeta.getInt(IJKM_KEY_TBR_NUM); + streamMeta.mTbrDen = streamMeta.getInt(IJKM_KEY_TBR_DEN); + streamMeta.mSarNum = streamMeta.getInt(IJKM_KEY_SAR_NUM); + streamMeta.mSarDen = streamMeta.getInt(IJKM_KEY_SAR_DEN); + + if (videoStreamIndex == index) { + meta.mVideoStream = streamMeta; + } + } else if (streamMeta.mType.equalsIgnoreCase(IJKM_VAL_TYPE__AUDIO)) { + streamMeta.mSampleRate = streamMeta + .getInt(IJKM_KEY_SAMPLE_RATE); + streamMeta.mChannelLayout = streamMeta + .getLong(IJKM_KEY_CHANNEL_LAYOUT); + + if (audioStreamIndex == index) { + meta.mAudioStream = streamMeta; + } + } + } + + return meta; + } + + public static class IjkStreamMeta { + public Bundle mMeta; + + public int mIndex; + public String mType; + + // common + public String mCodecName; + public String mCodecProfile; + public String mCodecLongName; + public long mBitrate; + + // video + public int mWidth; + public int mHeight; + public int mFpsNum; + public int mFpsDen; + public int mTbrNum; + public int mTbrDen; + public int mSarNum; + public int mSarDen; + + // audio + public int mSampleRate; + public long mChannelLayout; + + public IjkStreamMeta(int index) { + mIndex = index; + } + + public String getString(String key) { + return mMeta.getString(key); + } + + public int getInt(String key) { + return getInt(key, 0); + } + + public int getInt(String key, int defaultValue) { + String value = getString(key); + if (TextUtils.isEmpty(value)) + return defaultValue; + + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public long getLong(String key) { + return getLong(key, 0); + } + + public long getLong(String key, long defaultValue) { + String value = getString(key); + if (TextUtils.isEmpty(value)) + return defaultValue; + + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public String getCodecLongNameInline() { + if (!TextUtils.isEmpty(mCodecLongName)) { + return mCodecLongName; + } else if (!TextUtils.isEmpty(mCodecName)) { + return mCodecName; + } else { + return "N/A"; + } + } + + public String getResolutionInline() { + if (mWidth <= 0 || mHeight <= 0) { + return "N/A"; + } else if (mSarNum <= 0 || mSarDen <= 0) { + return String.format(Locale.US, "%d x %d", mWidth, mHeight); + } else { + return String.format(Locale.US, "%d x %d [SAR %d:%d]", mWidth, + mHeight, mSarNum, mSarDen); + } + } + + public String getFpsInline() { + if (mFpsNum <= 0 || mFpsDen <= 0) { + return "N/A"; + } else { + return String.valueOf(((float) (mFpsNum)) / mFpsDen); + } + } + + public String getBitrateInline() { + if (mBitrate <= 0) { + return "N/A"; + } else if (mBitrate < 1000) { + return String.format(Locale.US, "%d bit/s", mBitrate); + } else { + return String.format(Locale.US, "%d kb/s", mBitrate / 1000); + } + } + + public String getSampleRateInline() { + if (mSampleRate <= 0) { + return "N/A"; + } else { + return String.format(Locale.US, "%d Hz", mSampleRate); + } + } + + public String getChannelLayoutInline() { + if (mChannelLayout <= 0) { + return "N/A"; + } else { + if (mChannelLayout == AV_CH_LAYOUT_MONO) { + return "mono"; + } else if (mChannelLayout == AV_CH_LAYOUT_STEREO) { + return "stereo"; + } else { + return String.format(Locale.US, "%x", mChannelLayout); + } + } + } + } +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java new file mode 100755 index 0000000..7572c22 --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java @@ -0,0 +1,874 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * Copyright (C) 2013 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Locale; + +import tv.danmaku.ijk.media.player.annotations.AccessedByNative; +import tv.danmaku.ijk.media.player.annotations.CalledByNative; +import tv.danmaku.ijk.media.player.option.AvFormatOption; +import tv.danmaku.ijk.media.player.pragma.DebugLog; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; + +/** + * @author bbcallen + * + * Java wrapper of ffplay. + */ +public final class IjkMediaPlayer extends SimpleMediaPlayer { + private final static String TAG = IjkMediaPlayer.class.getName(); + + private static final int MEDIA_NOP = 0; // interface test message + private static final int MEDIA_PREPARED = 1; + private static final int MEDIA_PLAYBACK_COMPLETE = 2; + private static final int MEDIA_BUFFERING_UPDATE = 3; + private static final int MEDIA_SEEK_COMPLETE = 4; + private static final int MEDIA_SET_VIDEO_SIZE = 5; + private static final int MEDIA_TIMED_TEXT = 99; + private static final int MEDIA_ERROR = 100; + private static final int MEDIA_INFO = 200; + + protected static final int MEDIA_SET_VIDEO_SAR = 10001; + + //---------------------------------------- + // options + public static final int OPT_CATEGORY_FORMAT = 1; + public static final int OPT_CATEGORY_CODEC = 2; + public static final int OPT_CATEGORY_SWS = 3; + public static final int OPT_CATEGORY_PLAYER = 4; + + public static final int SDL_FCC_YV12 = 0x32315659; // YV12 + public static final int SDL_FCC_RV16 = 0x36315652; // RGB565 + public static final int SDL_FCC_RV32 = 0x32335652; // RGBX8888 + //---------------------------------------- + + @AccessedByNative + private long mNativeMediaPlayer; + + @AccessedByNative + private int mNativeSurfaceTexture; + + @AccessedByNative + private int mListenerContext; + + private SurfaceHolder mSurfaceHolder; + private EventHandler mEventHandler; + private PowerManager.WakeLock mWakeLock = null; + private boolean mScreenOnWhilePlaying; + private boolean mStayAwake; + + private int mVideoWidth; + private int mVideoHeight; + private int mVideoSarNum; + private int mVideoSarDen; + + private String mDataSource; + private String mFFConcatContent; + + /** + * Default library loader + * Load them by yourself, if your libraries are not installed at default place. + */ + private static IjkLibLoader sLocalLibLoader = new IjkLibLoader() { + @Override + public void loadLibrary(String libName) throws UnsatisfiedLinkError, SecurityException { + System.loadLibrary(libName); + } + }; + + private static volatile boolean mIsLibLoaded = false; + public static void loadLibrariesOnce(IjkLibLoader libLoader) { + synchronized (IjkMediaPlayer.class) { + if (!mIsLibLoaded) { + if (libLoader == null) + libLoader = sLocalLibLoader; + + libLoader.loadLibrary("ijkffmpeg"); + libLoader.loadLibrary("ijksdl"); + libLoader.loadLibrary("ijkplayer"); + mIsLibLoaded = true; + } + } + } + + private static volatile boolean mIsNativeInitialized = false; + private static void initNativeOnce() { + synchronized (IjkMediaPlayer.class) { + if (!mIsNativeInitialized) { + native_init(); + mIsNativeInitialized = true; + } + } + } + + /** + * Default constructor. Consider using one of the create() methods for + * synchronously instantiating a IjkMediaPlayer from a Uri or resource. + *

+ * When done with the IjkMediaPlayer, you should call {@link #release()}, to + * free the resources. If not released, too many IjkMediaPlayer instances + * may result in an exception. + *

+ */ + public IjkMediaPlayer() { + this(sLocalLibLoader); + } + + /** + * do not loadLibaray + * @param libLoader + * custom library loader, can be null. + */ + public IjkMediaPlayer(IjkLibLoader libLoader) { + initPlayer(libLoader); + } + + private void initPlayer(IjkLibLoader libLoader) { + loadLibrariesOnce(libLoader); + initNativeOnce(); + + Looper looper; + if ((looper = Looper.myLooper()) != null) { + mEventHandler = new EventHandler(this, looper); + } else if ((looper = Looper.getMainLooper()) != null) { + mEventHandler = new EventHandler(this, looper); + } else { + mEventHandler = null; + } + + /* + * Native setup requires a weak reference to our object. It's easier to + * create it here than in C++. + */ + native_setup(new WeakReference(this)); + } + + /* + * Update the IjkMediaPlayer SurfaceTexture. Call after setting a new + * display surface. + */ + private native void _setVideoSurface(Surface surface); + + /** + * Sets the {@link SurfaceHolder} to use for displaying the video portion of + * the media. + * + * Either a surface holder or surface must be set if a display or video sink + * is needed. Not calling this method or {@link #setSurface(Surface)} when + * playing back a video will result in only the audio track being played. A + * null surface holder or surface will result in only the audio track being + * played. + * + * @param sh + * the SurfaceHolder to use for video display + */ + @Override + public void setDisplay(SurfaceHolder sh) { + mSurfaceHolder = sh; + Surface surface; + if (sh != null) { + surface = sh.getSurface(); + } else { + surface = null; + } + _setVideoSurface(surface); + updateSurfaceScreenOn(); + } + + /** + * Sets the {@link Surface} to be used as the sink for the video portion of + * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but + * does not support {@link #setScreenOnWhilePlaying(boolean)}. Setting a + * Surface will un-set any Surface or SurfaceHolder that was previously set. + * A null surface will result in only the audio track being played. + * + * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps + * returned from {@link SurfaceTexture#getTimestamp()} will have an + * unspecified zero point. These timestamps cannot be directly compared + * between different media sources, different instances of the same media + * source, or multiple runs of the same program. The timestamp is normally + * monotonically increasing and is unaffected by time-of-day adjustments, + * but it is reset when the position is set. + * + * @param surface + * The {@link Surface} to be used for the video portion of the + * media. + */ + @Override + public void setSurface(Surface surface) { + if (mScreenOnWhilePlaying && surface != null) { + DebugLog.w(TAG, + "setScreenOnWhilePlaying(true) is ineffective for Surface"); + } + mSurfaceHolder = null; + _setVideoSurface(surface); + updateSurfaceScreenOn(); + } + + /** + * Sets the data source (file-path or http/rtsp URL) to use. + * + * @param path + * the path of the file, or the http/rtsp URL of the stream you + * want to play + * @throws IllegalStateException + * if it is called in an invalid state + * + *

+ * When path refers to a local file, the file may + * actually be opened by a process other than the calling + * application. This implies that the pathname should be an + * absolute path (as any other process runs with unspecified + * current working directory), and that the pathname should + * reference a world-readable file. + */ + @Override + public void setDataSource(String path) throws IOException, + IllegalArgumentException, SecurityException, IllegalStateException { + mDataSource = path; + _setDataSource(path, null, null); + } + + private native void _setDataSource(String path, String[] keys, + String[] values) throws IOException, IllegalArgumentException, + SecurityException, IllegalStateException; + + @Override + public String getDataSource() { + return mDataSource; + } + + public void setDataSourceAsFFConcatContent(String ffConcatContent) { + mFFConcatContent = ffConcatContent; + } + + @Override + public void prepareAsync() throws IllegalStateException { + if (TextUtils.isEmpty(mFFConcatContent)) { + _prepareAsync(); + } else { + _prepareAsync(); + } + } + + public native void _prepareAsync() throws IllegalStateException; + + @Override + public void start() throws IllegalStateException { + stayAwake(true); + _start(); + } + + private native void _start() throws IllegalStateException; + + @Override + public void stop() throws IllegalStateException { + stayAwake(false); + _stop(); + } + + private native void _stop() throws IllegalStateException; + + @Override + public void pause() throws IllegalStateException { + stayAwake(false); + _pause(); + } + + private native void _pause() throws IllegalStateException; + + @SuppressLint("Wakelock") + @Override + public void setWakeMode(Context context, int mode) { + boolean washeld = false; + if (mWakeLock != null) { + if (mWakeLock.isHeld()) { + washeld = true; + mWakeLock.release(); + } + mWakeLock = null; + } + + PowerManager pm = (PowerManager) context + .getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(mode | PowerManager.ON_AFTER_RELEASE, + IjkMediaPlayer.class.getName()); + mWakeLock.setReferenceCounted(false); + if (washeld) { + mWakeLock.acquire(); + } + } + + @Override + public void setScreenOnWhilePlaying(boolean screenOn) { + if (mScreenOnWhilePlaying != screenOn) { + if (screenOn && mSurfaceHolder == null) { + DebugLog.w(TAG, + "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder"); + } + mScreenOnWhilePlaying = screenOn; + updateSurfaceScreenOn(); + } + } + + @SuppressLint("Wakelock") + private void stayAwake(boolean awake) { + if (mWakeLock != null) { + if (awake && !mWakeLock.isHeld()) { + mWakeLock.acquire(); + } else if (!awake && mWakeLock.isHeld()) { + mWakeLock.release(); + } + } + mStayAwake = awake; + updateSurfaceScreenOn(); + } + + private void updateSurfaceScreenOn() { + if (mSurfaceHolder != null) { + mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake); + } + } + + @Override + public int getVideoWidth() { + return mVideoWidth; + } + + @Override + public int getVideoHeight() { + return mVideoHeight; + } + + @Override + public int getVideoSarNum() { + return mVideoSarNum; + } + + @Override + public int getVideoSarDen() { + return mVideoSarDen; + } + + @Override + public native boolean isPlaying(); + + @Override + public native void seekTo(long msec) throws IllegalStateException; + + @Override + public native long getCurrentPosition(); + + @Override + public native long getDuration(); + + /** + * Releases resources associated with this IjkMediaPlayer object. It is + * considered good practice to call this method when you're done using the + * IjkMediaPlayer. In particular, whenever an Activity of an application is + * paused (its onPause() method is called), or stopped (its onStop() method + * is called), this method should be invoked to release the IjkMediaPlayer + * object, unless the application has a special need to keep the object + * around. In addition to unnecessary resources (such as memory and + * instances of codecs) being held, failure to call this method immediately + * if a IjkMediaPlayer object is no longer needed may also lead to + * continuous battery consumption for mobile devices, and playback failure + * for other applications if no multiple instances of the same codec are + * supported on a device. Even if multiple instances of the same codec are + * supported, some performance degradation may be expected when unnecessary + * multiple instances are used at the same time. + */ + @Override + public void release() { + stayAwake(false); + updateSurfaceScreenOn(); + resetListeners(); + _release(); + } + + private native void _release(); + + @Override + public void reset() { + stayAwake(false); + _reset(); + // make sure none of the listeners get called anymore + mEventHandler.removeCallbacksAndMessages(null); + + mVideoWidth = 0; + mVideoHeight = 0; + } + + private native void _reset(); + + public native void setVolume(float leftVolume, float rightVolume); + + @Override + public MediaInfo getMediaInfo() { + MediaInfo mediaInfo = new MediaInfo(); + mediaInfo.mMediaPlayerName = "ijkplayer"; + + String videoCodecInfo = _getVideoCodecInfo(); + if (!TextUtils.isEmpty(videoCodecInfo)) { + String nodes[] = videoCodecInfo.split(","); + if (nodes.length >= 2) { + mediaInfo.mVideoDecoder = nodes[0]; + mediaInfo.mVideoDecoderImpl = nodes[1]; + } else if (nodes.length >= 1) { + mediaInfo.mVideoDecoder = nodes[0]; + mediaInfo.mVideoDecoderImpl = ""; + } + } + + String audioCodecInfo = _getAudioCodecInfo(); + if (!TextUtils.isEmpty(audioCodecInfo)) { + String nodes[] = audioCodecInfo.split(","); + if (nodes.length >= 2) { + mediaInfo.mAudioDecoder = nodes[0]; + mediaInfo.mAudioDecoderImpl = nodes[1]; + } else if (nodes.length >= 1) { + mediaInfo.mAudioDecoder = nodes[0]; + mediaInfo.mAudioDecoderImpl = ""; + } + } + + try { + mediaInfo.mMeta = IjkMediaMeta.parse(_getMediaMeta()); + } catch (Throwable e) { + e.printStackTrace(); + } + return mediaInfo; + } + + private native String _getVideoCodecInfo(); + private native String _getAudioCodecInfo(); + + public void setOption(int category, String name, String value) + { + _setOption(category, name, value); + } + + public void setOption(int category, String name, long value) + { + _setOption(category, name, value); + } + + @Deprecated + public void setAvOption(AvFormatOption option) { + setAvFormatOption(option.getName(), option.getValue()); + } + + @Deprecated + public void setAvFormatOption(String name, String value) { + setOption(OPT_CATEGORY_FORMAT, name, value); + } + + @Deprecated + public void setAvCodecOption(String name, String value) { + setOption(OPT_CATEGORY_CODEC, name, value); + } + + @Deprecated + public void setSwScaleOption(String name, String value) { + setOption(OPT_CATEGORY_SWS, name, value); + } + + @Deprecated + public void setOverlayFormat(int chromaFourCC) { + setOption(OPT_CATEGORY_PLAYER, "overlay-format", chromaFourCC); + } + + @Deprecated + public void setFrameDrop(int frameDrop) { + setOption(OPT_CATEGORY_PLAYER, "framedrop", frameDrop); + } + + @Deprecated + public void setMediaCodecEnabled(boolean enabled) { + setOption(OPT_CATEGORY_PLAYER, "mediacodec", enabled ? 1 : 0); + } + + @Deprecated + public void setOpenSLESEnabled(boolean enabled) { + setOption(OPT_CATEGORY_PLAYER, "opengles", enabled ? 1 : 0); + } + + @Deprecated + public void setAutoPlayOnPrepared(boolean enabled) { + setOption(OPT_CATEGORY_PLAYER, "start-on-prepared", enabled ? 1 : 0); + } + + private native void _setOption(int category, String name, String value); + private native void _setOption(int category, String name, long value); + + public Bundle getMediaMeta() { + return _getMediaMeta(); + } + private native Bundle _getMediaMeta(); + + public static String getColorFormatName(int mediaCodecColorFormat) { + return _getColorFormatName(mediaCodecColorFormat); + } + + private static native String _getColorFormatName(int mediaCodecColorFormat); + + @Override + public void setAudioStreamType(int streamtype) { + // do nothing + } + + private static native void native_init(); + + private native void native_setup(Object IjkMediaPlayer_this); + + private native void native_finalize(); + + private native void native_message_loop(Object IjkMediaPlayer_this); + + protected void finalize() { + native_finalize(); + } + + private static class EventHandler extends Handler { + private WeakReference mWeakPlayer; + + public EventHandler(IjkMediaPlayer mp, Looper looper) { + super(looper); + mWeakPlayer = new WeakReference(mp); + } + + @Override + public void handleMessage(Message msg) { + IjkMediaPlayer player = mWeakPlayer.get(); + if (player == null || player.mNativeMediaPlayer == 0) { + DebugLog.w(TAG, + "IjkMediaPlayer went away with unhandled events"); + return; + } + + switch (msg.what) { + case MEDIA_PREPARED: + player.notifyOnPrepared(); + return; + + case MEDIA_PLAYBACK_COMPLETE: + player.notifyOnCompletion(); + player.stayAwake(false); + return; + + case MEDIA_BUFFERING_UPDATE: + long bufferPosition = msg.arg1; + if (bufferPosition < 0) { + bufferPosition = 0; + } + + long percent = 0; + long duration = player.getDuration(); + if (duration > 0) { + percent = bufferPosition * 100 / duration; + } + if (percent >= 100) { + percent = 100; + } + + // DebugLog.efmt(TAG, "Buffer (%d%%) %d/%d", percent, bufferPosition, duration); + player.notifyOnBufferingUpdate((int)percent); + return; + + case MEDIA_SEEK_COMPLETE: + player.notifyOnSeekComplete(); + return; + + case MEDIA_SET_VIDEO_SIZE: + player.mVideoWidth = msg.arg1; + player.mVideoHeight = msg.arg2; + player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight, + player.mVideoSarNum, player.mVideoSarDen); + return; + + case MEDIA_ERROR: + DebugLog.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")"); + if (!player.notifyOnError(msg.arg1, msg.arg2)) { + player.notifyOnCompletion(); + } + player.stayAwake(false); + return; + + case MEDIA_INFO: + if (msg.arg1 != MEDIA_INFO_VIDEO_TRACK_LAGGING) { + DebugLog.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")"); + } + player.notifyOnInfo(msg.arg1, msg.arg2); + // No real default action so far. + return; + case MEDIA_TIMED_TEXT: + // do nothing + break; + + case MEDIA_NOP: // interface test message - ignore + break; + + case MEDIA_SET_VIDEO_SAR: + player.mVideoSarNum = msg.arg1; + player.mVideoSarDen = msg.arg2; + player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight, + player.mVideoSarNum, player.mVideoSarDen); + break; + + default: + DebugLog.e(TAG, "Unknown message type " + msg.what); + return; + } + } + } + + /* + * Called from native code when an interesting event happens. This method + * just uses the EventHandler system to post the event back to the main app + * thread. We use a weak reference to the original IjkMediaPlayer object so + * that the native code is safe from the object disappearing from underneath + * it. (This is the cookie passed to native_setup().) + */ + @CalledByNative + private static void postEventFromNative(Object weakThiz, int what, + int arg1, int arg2, Object obj) { + if (weakThiz == null) + return; + + @SuppressWarnings("rawtypes") + IjkMediaPlayer mp = (IjkMediaPlayer) ((WeakReference) weakThiz).get(); + if (mp == null) { + return; + } + + if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) { + // this acquires the wakelock if needed, and sets the client side + // state + mp.start(); + } + if (mp.mEventHandler != null) { + Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj); + mp.mEventHandler.sendMessage(m); + } + } + + private OnControlMessageListener mOnControlMessageListener; + public void setOnControlMessageListener(OnControlMessageListener listener) { + mOnControlMessageListener = listener; + } + + public static interface OnControlMessageListener { + public int onControlResolveSegmentCount(); + public String onControlResolveSegmentUrl(int segment); + public String onControlResolveSegmentOfflineMrl(int segment); + public int onControlResolveSegmentDuration(int segment); + } + + @CalledByNative + private static int onControlResolveSegmentCount(Object weakThiz) { + DebugLog.ifmt(TAG, "onControlResolveSegmentCount"); + if (weakThiz == null || !(weakThiz instanceof WeakReference)) + return -1; + + @SuppressWarnings("unchecked") + WeakReference weakPlayer = (WeakReference) weakThiz; + IjkMediaPlayer player = weakPlayer.get(); + if (player == null) + return -1; + + OnControlMessageListener listener = player.mOnControlMessageListener; + if (listener == null) + return -1; + + return listener.onControlResolveSegmentCount(); + } + + @CalledByNative + private static String onControlResolveSegmentUrl(Object weakThiz, int segment) { + DebugLog.ifmt(TAG, "onControlResolveSegmentUrl %d", segment); + if (weakThiz == null || !(weakThiz instanceof WeakReference)) + return null; + + @SuppressWarnings("unchecked") + WeakReference weakPlayer = (WeakReference) weakThiz; + IjkMediaPlayer player = weakPlayer.get(); + if (player == null) + return null; + + OnControlMessageListener listener = player.mOnControlMessageListener; + if (listener == null) + return null; + + return listener.onControlResolveSegmentUrl(segment); + } + + @CalledByNative + private static String onControlResolveSegmentOfflineMrl(Object weakThiz, int segment) { + DebugLog.ifmt(TAG, "onControlResolveSegmentOfflineMrl %d", segment); + if (weakThiz == null || !(weakThiz instanceof WeakReference)) + return null; + + @SuppressWarnings("unchecked") + WeakReference weakPlayer = (WeakReference) weakThiz; + IjkMediaPlayer player = weakPlayer.get(); + if (player == null) + return null; + + OnControlMessageListener listener = player.mOnControlMessageListener; + if (listener == null) + return null; + + return listener.onControlResolveSegmentOfflineMrl(segment); + } + + @CalledByNative + private static int onControlResolveSegmentDuration(Object weakThiz, int segment) { + DebugLog.ifmt(TAG, "onControlResolveSegmentDuration %d", segment); + if (weakThiz == null || !(weakThiz instanceof WeakReference)) + return -1; + + @SuppressWarnings("unchecked") + WeakReference weakPlayer = (WeakReference) weakThiz; + IjkMediaPlayer player = weakPlayer.get(); + if (player == null) + return -1; + + OnControlMessageListener listener = player.mOnControlMessageListener; + if (listener == null) + return -1; + + return listener.onControlResolveSegmentDuration(segment); + } + + public static interface OnMediaCodecSelectListener { + public String onMediaCodecSelect(IMediaPlayer mp, String mimeType, int profile, int level); + } + private OnMediaCodecSelectListener mOnMediaCodecSelectListener; + public void setOnMediaCodecSelectListener(OnMediaCodecSelectListener listener) { + mOnMediaCodecSelectListener = listener; + } + + public void resetListeners() { + super.resetListeners(); + mOnMediaCodecSelectListener = null; + } + + @CalledByNative + private static String onSelectCodec(Object weakThiz, String mimeType, int profile, int level) { + if (weakThiz == null || !(weakThiz instanceof WeakReference)) + return null; + + @SuppressWarnings("unchecked") + WeakReference weakPlayer = (WeakReference) weakThiz; + IjkMediaPlayer player = weakPlayer.get(); + if (player == null) + return null; + + OnMediaCodecSelectListener listener = player.mOnMediaCodecSelectListener; + if (listener == null) + listener = DefaultMediaCodecSelector.sInstance; + + return listener.onMediaCodecSelect(player, mimeType, profile, level); + } + + public static class DefaultMediaCodecSelector implements OnMediaCodecSelectListener { + public static DefaultMediaCodecSelector sInstance = new DefaultMediaCodecSelector(); + + @SuppressWarnings("deprecation") + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public String onMediaCodecSelect(IMediaPlayer mp, String mimeType, int profile, int level) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + return null; + + if (TextUtils.isEmpty(mimeType)) + return null; + + Log.i(TAG, String.format(Locale.US, "onSelectCodec: mime=%s, profile=%d, level=%d", mimeType, profile, level)); + ArrayList candidateCodecList = new ArrayList(); + int numCodecs = MediaCodecList.getCodecCount(); + for (int i = 0; i < numCodecs; i++) { + MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + Log.d(TAG, String.format(Locale.US, " found codec: %s", codecInfo.getName())); + if (codecInfo.isEncoder()) + continue; + + String[] types = codecInfo.getSupportedTypes(); + if (types == null) + continue; + + for(String type: types) { + if (TextUtils.isEmpty(type)) + continue; + + Log.d(TAG, String.format(Locale.US, " mime: %s", type)); + if (!type.equalsIgnoreCase(mimeType)) + continue; + + IjkMediaCodecInfo candidate = IjkMediaCodecInfo.setupCandidate(codecInfo, mimeType); + if (candidate == null) + continue; + + candidateCodecList.add(candidate); + Log.i(TAG, String.format(Locale.US, "candidate codec: %s rank=%d", codecInfo.getName(), candidate.mRank)); + candidate.dumpProfileLevels(mimeType); + } + } + + if (candidateCodecList.isEmpty()) { + return null; + } + + IjkMediaCodecInfo bestCodec = candidateCodecList.get(0); + + for (IjkMediaCodecInfo codec : candidateCodecList) { + if (codec.mRank > bestCodec.mRank) { + bestCodec = codec; + } + } + + if (bestCodec.mRank < IjkMediaCodecInfo.RANK_LAST_CHANCE) { + Log.w(TAG, String.format(Locale.US, "unaccetable codec: %s", bestCodec.mCodecInfo.getName())); + return null; + } + + Log.i(TAG, String.format(Locale.US, "selected codec: %s rank=%d", bestCodec.mCodecInfo.getName(), bestCodec.mRank)); + return bestCodec.mCodecInfo.getName(); + } + } + + public static native void native_profileBegin(String libName); + public static native void native_profileEnd(); +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java new file mode 100755 index 0000000..6cd5004 --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2013-2014 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player; + +public class MediaInfo { + public String mMediaPlayerName; + + public String mVideoDecoder; + public String mVideoDecoderImpl; + + public String mAudioDecoder; + public String mAudioDecoderImpl; + + public IjkMediaMeta mMeta; +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/SimpleMediaPlayer.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/SimpleMediaPlayer.java new file mode 100755 index 0000000..6c3f626 --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/SimpleMediaPlayer.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2013-2014 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player; + +/** + * @author bbcallen + * + * Common IMediaPlayer implement + */ +public abstract class SimpleMediaPlayer extends BaseMediaPlayer implements + IMediaPlayer { + private OnPreparedListener mOnPreparedListener; + private OnCompletionListener mOnCompletionListener; + private OnBufferingUpdateListener mOnBufferingUpdateListener; + private OnSeekCompleteListener mOnSeekCompleteListener; + private OnVideoSizeChangedListener mOnVideoSizeChangedListener; + private OnErrorListener mOnErrorListener; + private OnInfoListener mOnInfoListener; + + public final void setOnPreparedListener(OnPreparedListener listener) { + mOnPreparedListener = listener; + } + + public final void setOnCompletionListener(OnCompletionListener listener) { + mOnCompletionListener = listener; + } + + public final void setOnBufferingUpdateListener( + OnBufferingUpdateListener listener) { + mOnBufferingUpdateListener = listener; + } + + public final void setOnSeekCompleteListener(OnSeekCompleteListener listener) { + mOnSeekCompleteListener = listener; + } + + public final void setOnVideoSizeChangedListener( + OnVideoSizeChangedListener listener) { + mOnVideoSizeChangedListener = listener; + } + + public final void setOnErrorListener(OnErrorListener listener) { + mOnErrorListener = listener; + } + + public final void setOnInfoListener(OnInfoListener listener) { + mOnInfoListener = listener; + } + + public void resetListeners() { + mOnPreparedListener = null; + mOnBufferingUpdateListener = null; + mOnCompletionListener = null; + mOnSeekCompleteListener = null; + mOnVideoSizeChangedListener = null; + mOnErrorListener = null; + mOnInfoListener = null; + } + + public void attachListeners(IMediaPlayer mp) { + mp.setOnPreparedListener(mOnPreparedListener); + mp.setOnBufferingUpdateListener(mOnBufferingUpdateListener); + mp.setOnCompletionListener(mOnCompletionListener); + mp.setOnSeekCompleteListener(mOnSeekCompleteListener); + mp.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener); + mp.setOnErrorListener(mOnErrorListener); + mp.setOnInfoListener(mOnInfoListener); + } + + protected final void notifyOnPrepared() { + if (mOnPreparedListener != null) + mOnPreparedListener.onPrepared(this); + } + + protected final void notifyOnCompletion() { + if (mOnCompletionListener != null) + mOnCompletionListener.onCompletion(this); + } + + protected final void notifyOnBufferingUpdate(int percent) { + if (mOnBufferingUpdateListener != null) + mOnBufferingUpdateListener.onBufferingUpdate(this, percent); + } + + protected final void notifyOnSeekComplete() { + if (mOnSeekCompleteListener != null) + mOnSeekCompleteListener.onSeekComplete(this); + } + + protected final void notifyOnVideoSizeChanged(int width, int height, + int sarNum, int sarDen) { + if (mOnVideoSizeChangedListener != null) + mOnVideoSizeChangedListener.onVideoSizeChanged(this, width, height, + sarNum, sarDen); + } + + protected final boolean notifyOnError(int what, int extra) { + if (mOnErrorListener != null) + return mOnErrorListener.onError(this, what, extra); + return false; + } + + protected final boolean notifyOnInfo(int what, int extra) { + if (mOnInfoListener != null) + return mOnInfoListener.onInfo(this, what, extra); + return false; + } +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/annotations/AccessedByNative.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/annotations/AccessedByNative.java new file mode 100755 index 0000000..60db0d2 --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/annotations/AccessedByNative.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013-2014 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNative is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.CLASS) +public @interface AccessedByNative { +} \ No newline at end of file diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/annotations/CalledByNative.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/annotations/CalledByNative.java new file mode 100755 index 0000000..1595f80 --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/annotations/CalledByNative.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013-2014 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNative is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface CalledByNative { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +} \ No newline at end of file diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/exceptions/IjkMediaException.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/exceptions/IjkMediaException.java new file mode 100755 index 0000000..dbd1add --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/exceptions/IjkMediaException.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2013-2014 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player.exceptions; + +public class IjkMediaException extends Exception { + private static final long serialVersionUID = 7234796519009099506L; +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/ffmpeg/FFmpegApi.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/ffmpeg/FFmpegApi.java new file mode 100755 index 0000000..e13ba1b --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/ffmpeg/FFmpegApi.java @@ -0,0 +1,5 @@ +package tv.danmaku.ijk.media.player.ffmpeg; + +public class FFmpegApi { + public static native String av_base64_encode(byte in[]); +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/option/AvFormatOption.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/option/AvFormatOption.java new file mode 100755 index 0000000..929359a --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/option/AvFormatOption.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013-2014 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player.option; + +@Deprecated +public interface AvFormatOption { + public abstract String getName(); + + public abstract String getValue(); +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/option/AvFourCC.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/option/AvFourCC.java new file mode 100755 index 0000000..cff1c5d --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/option/AvFourCC.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013-2014 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player.option; + +@Deprecated +public class AvFourCC { + public static int SDL_FCC_YV12 = 0x32315659; // YV12 + public static int SDL_FCC_RV16 = 0x36315652; // RGB565 + public static int SDL_FCC_RV32 = 0x32335652; // RGBX8888 +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/option/format/AvFormatOption_HttpDetectRangeSupport.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/option/format/AvFormatOption_HttpDetectRangeSupport.java new file mode 100755 index 0000000..727552a --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/option/format/AvFormatOption_HttpDetectRangeSupport.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013-2014 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player.option.format; + +import tv.danmaku.ijk.media.player.option.AvFormatOption; + +// some video servers do not accept "Range: bytes=0-" +public final class AvFormatOption_HttpDetectRangeSupport implements + AvFormatOption { + public static AvFormatOption_HttpDetectRangeSupport Enable = new AvFormatOption_HttpDetectRangeSupport( + "1"); + public static AvFormatOption_HttpDetectRangeSupport Disable = new AvFormatOption_HttpDetectRangeSupport( + "0"); + private final String mValue; + + public AvFormatOption_HttpDetectRangeSupport(String value) { + mValue = value; + } + + public String getName() { + return "http-detect-range-support"; + } + + public String getValue() { + return mValue; + } +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/pragma/DebugLog.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/pragma/DebugLog.java new file mode 100755 index 0000000..fe3382e --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/pragma/DebugLog.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2013 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.player.pragma; + +import java.util.Locale; + + +import android.util.Log; + +public class DebugLog { + public static final boolean ENABLE_ERROR = Pragma.ENABLE_VERBOSE; + public static final boolean ENABLE_INFO = Pragma.ENABLE_VERBOSE; + public static final boolean ENABLE_WARN = Pragma.ENABLE_VERBOSE; + public static final boolean ENABLE_DEBUG = Pragma.ENABLE_VERBOSE; + public static final boolean ENABLE_VERBOSE = Pragma.ENABLE_VERBOSE; + + public static int e(String tag, String msg) { + if (ENABLE_ERROR) { + return Log.e(tag, msg); + } + + return 0; + } + + public static int e(String tag, String msg, Throwable tr) { + if (ENABLE_ERROR) { + return Log.e(tag, msg, tr); + } + + return 0; + } + + public static int efmt(String tag, String fmt, Object... args) { + if (ENABLE_ERROR) { + String msg = String.format(Locale.US, fmt, args); + return Log.e(tag, msg); + } + + return 0; + } + + public static int i(String tag, String msg) { + if (ENABLE_INFO) { + return Log.i(tag, msg); + } + + return 0; + } + + public static int i(String tag, String msg, Throwable tr) { + if (ENABLE_INFO) { + return Log.i(tag, msg, tr); + } + + return 0; + } + + public static int ifmt(String tag, String fmt, Object... args) { + if (ENABLE_INFO) { + String msg = String.format(Locale.US, fmt, args); + return Log.i(tag, msg); + } + + return 0; + } + + public static int w(String tag, String msg) { + if (ENABLE_WARN) { + return Log.w(tag, msg); + } + + return 0; + } + + public static int w(String tag, String msg, Throwable tr) { + if (ENABLE_WARN) { + return Log.w(tag, msg, tr); + } + + return 0; + } + + public static int wfmt(String tag, String fmt, Object... args) { + if (ENABLE_WARN) { + String msg = String.format(Locale.US, fmt, args); + return Log.w(tag, msg); + } + + return 0; + } + + public static int d(String tag, String msg) { + if (ENABLE_DEBUG) { + return Log.d(tag, msg); + } + + return 0; + } + + public static int d(String tag, String msg, Throwable tr) { + if (ENABLE_DEBUG) { + return Log.d(tag, msg, tr); + } + + return 0; + } + + public static int dfmt(String tag, String fmt, Object... args) { + if (ENABLE_DEBUG) { + String msg = String.format(Locale.US, fmt, args); + return Log.d(tag, msg); + } + + return 0; + } + + public static int v(String tag, String msg) { + if (ENABLE_VERBOSE) { + return Log.v(tag, msg); + } + + return 0; + } + + public static int v(String tag, String msg, Throwable tr) { + if (ENABLE_VERBOSE) { + return Log.v(tag, msg, tr); + } + + return 0; + } + + public static int vfmt(String tag, String fmt, Object... args) { + if (ENABLE_VERBOSE) { + String msg = String.format(Locale.US, fmt, args); + return Log.v(tag, msg); + } + + return 0; + } + + public static void printStackTrace(Throwable e) { + if (ENABLE_WARN) { + e.printStackTrace(); + } + } + + public static void printCause(Throwable e) { + if (ENABLE_WARN) { + Throwable cause = e.getCause(); + if (cause != null) + e = cause; + + printStackTrace(e); + } + } +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/pragma/Pragma.java b/player-java/src/main/java/tv/danmaku/ijk/media/player/pragma/Pragma.java new file mode 100755 index 0000000..df26120 --- /dev/null +++ b/player-java/src/main/java/tv/danmaku/ijk/media/player/pragma/Pragma.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2013 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tv.danmaku.ijk.media.player.pragma; + +/*- + * configurated by app project + */ +public class Pragma { + public static final boolean ENABLE_VERBOSE = true; +} diff --git a/player-java/src/main/project.properties b/player-java/src/main/project.properties new file mode 100755 index 0000000..362a0a3 --- /dev/null +++ b/player-java/src/main/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-22 +android.library=true diff --git a/player-java/src/main/res/values/strings.xml b/player-java/src/main/res/values/strings.xml new file mode 100755 index 0000000..7356db3 --- /dev/null +++ b/player-java/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/sample/.gitignore b/sample/.gitignore new file mode 100755 index 0000000..796b96d --- /dev/null +++ b/sample/.gitignore @@ -0,0 +1 @@ +/build diff --git a/sample/build.gradle b/sample/build.gradle new file mode 100755 index 0000000..4eed8c6 --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' + +android { + // http://tools.android.com/tech-docs/new-build-system/tips + //noinspection GroovyAssignabilityCheck + compileSdkVersion rootProject.ext.compileSdkVersion + //noinspection GroovyAssignabilityCheck + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + applicationId "tv.danmaku.ijk.media.sample" + minSdkVersion 9 + targetSdkVersion 22 + versionCode 301000 + versionName "0.3.1" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + compile 'com.android.support:appcompat-v7:22.2.0' + compile project(':player-java') + // need API-21 + // compile project(':player-arm64') +} diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro new file mode 100755 index 0000000..034485d --- /dev/null +++ b/sample/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /opt/android/ADK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/sample/sample.iml b/sample/sample.iml new file mode 100644 index 0000000..b8e224c --- /dev/null +++ b/sample/sample.iml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/androidTest/java/tv/danmaku/ijk/media/sample/ApplicationTest.java b/sample/src/androidTest/java/tv/danmaku/ijk/media/sample/ApplicationTest.java new file mode 100755 index 0000000..699ac1c --- /dev/null +++ b/sample/src/androidTest/java/tv/danmaku/ijk/media/sample/ApplicationTest.java @@ -0,0 +1,13 @@ +package tv.danmaku.ijk.media.sample; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/sample/src/main/.classpath b/sample/src/main/.classpath new file mode 100755 index 0000000..a5918a0 --- /dev/null +++ b/sample/src/main/.classpath @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/sample/src/main/.project b/sample/src/main/.project new file mode 100755 index 0000000..0a083ee --- /dev/null +++ b/sample/src/main/.project @@ -0,0 +1,33 @@ + + + ijkplayer-sample + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/sample/src/main/.settings/org.eclipse.jdt.core.prefs b/sample/src/main/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 0000000..b080d2d --- /dev/null +++ b/sample/src/main/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml new file mode 100755 index 0000000..ea925d6 --- /dev/null +++ b/sample/src/main/AndroidManifest.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/java/tv/danmaku/ijk/media/sample/FileListActivity.java b/sample/src/main/java/tv/danmaku/ijk/media/sample/FileListActivity.java new file mode 100755 index 0000000..dc15996 --- /dev/null +++ b/sample/src/main/java/tv/danmaku/ijk/media/sample/FileListActivity.java @@ -0,0 +1,132 @@ +package tv.danmaku.ijk.media.sample; + +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.provider.MediaStore; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.widget.SimpleCursorAdapter; +import android.support.v7.app.ActionBarActivity; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; + + +public class FileListActivity extends ActionBarActivity implements LoaderManager.LoaderCallbacks { + + ListView fileListView; + VideoAdapter adapter; + + boolean sortByName = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_file_list); + + fileListView = (ListView) findViewById(R.id.fileListView); + adapter = new VideoAdapter(this); + fileListView.setAdapter(adapter); + fileListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, final int position, final long id) { + Intent intent = new Intent(FileListActivity.this, VideoPlayerActivity.class); + intent.putExtra("videoPath", adapter.getVideoPath(position)); + intent.putExtra("videoTitle", adapter.getVideoTitle(position)); + startActivity(intent); + } + }); + + getSupportLoaderManager().initLoader(1, null, this); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_file_list, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_sort) { + sortByName = !sortByName; + getSupportLoaderManager().restartLoader(1, null, this); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + if (sortByName) { + return new CursorLoader(this, MediaStore.Video.Media.getContentUri("external"), null, null, null, + "UPPER(" + MediaStore.Video.Media.DATA + ")"); + } else { + return new CursorLoader(this, MediaStore.Video.Media.getContentUri("external"), null, null, null, + "UPPER(" + MediaStore.Video.Media.DISPLAY_NAME + ")"); + } + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + adapter.swapCursor(data); + } + + @Override + public void onLoaderReset(Loader loader) { + + } + + class VideoAdapter extends SimpleCursorAdapter { + public VideoAdapter(Context context) { + super(context, android.R.layout.simple_list_item_2, null, + new String[]{MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DATA}, + new int[]{android.R.id.text1, android.R.id.text2}, 0); + } + + @Override + public long getItemId(int position) { + final Cursor cursor = getCursor(); + if (cursor.getCount() == 0 || position >= cursor.getCount()) { + return 0; + } + cursor.moveToPosition(position); + + return cursor.getLong(0); + } + + public String getVideoTitle(int position) { + final Cursor cursor = getCursor(); + if (cursor.getCount() == 0) { + return ""; + } + cursor.moveToPosition(position); + + return cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME)); + } + + public String getVideoPath(int position) { + final Cursor cursor = getCursor(); + if (cursor.getCount() == 0) { + return ""; + } + cursor.moveToPosition(position); + + return cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); + } + } +} diff --git a/sample/src/main/java/tv/danmaku/ijk/media/sample/VideoPlayerActivity.java b/sample/src/main/java/tv/danmaku/ijk/media/sample/VideoPlayerActivity.java new file mode 100755 index 0000000..83f2c3b --- /dev/null +++ b/sample/src/main/java/tv/danmaku/ijk/media/sample/VideoPlayerActivity.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2013 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.sample; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + +import tv.danmaku.ijk.media.player.IjkMediaPlayer; +import tv.danmaku.ijk.media.widget.MediaController; +import tv.danmaku.ijk.media.widget.VideoView; + +public class VideoPlayerActivity extends Activity { + private VideoView mVideoView; + private View mBufferingIndicator; + private MediaController mMediaController; + + private String mVideoPath = "http://pili-playback.wscn.wallstcn.com/wscn/chat_51_1222023523_rebirth_wallstcn_com.m3u8?start=0&end=0"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_player); + + IjkMediaPlayer.loadLibrariesOnce(null); + IjkMediaPlayer.native_profileBegin("libijkplayer.so"); + + // mVideoPath = getIntent().getStringExtra("videoPath"); + + Intent intent = getIntent(); + String intentAction = intent.getAction(); +/* if (!TextUtils.isEmpty(intentAction) && intentAction.equals(Intent.ACTION_VIEW)) { + mVideoPath = intent.getDataString(); + }*/ + + mBufferingIndicator = findViewById(R.id.buffering_indicator); + mMediaController = new MediaController(this); + + mVideoView = (VideoView) findViewById(R.id.video_view); + mVideoView.setMediaController(mMediaController); + mVideoView.setMediaBufferingIndicator(mBufferingIndicator); + mVideoView.setVideoPath(mVideoPath); + mVideoView.requestFocus(); + mVideoView.start(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + IjkMediaPlayer.native_profileEnd(); + } +} diff --git a/sample/src/main/java/tv/danmaku/ijk/media/widget/CenterLayout.java b/sample/src/main/java/tv/danmaku/ijk/media/widget/CenterLayout.java new file mode 100755 index 0000000..cbf6a28 --- /dev/null +++ b/sample/src/main/java/tv/danmaku/ijk/media/widget/CenterLayout.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012 YIXIA.COM + * Copyright (C) 2013 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RemoteViews.RemoteView; + +@RemoteView +public class CenterLayout extends ViewGroup { + private int mPaddingLeft = 0; + private int mPaddingRight = 0; + private int mPaddingTop = 0; + private int mPaddingBottom = 0; + private int mWidth, mHeight; + + public CenterLayout(Context context) { + super(context); + } + + public CenterLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CenterLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int count = getChildCount(); + + int maxHeight = 0; + int maxWidth = 0; + + measureChildren(widthMeasureSpec, heightMeasureSpec); + + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + int childRight; + int childBottom; + + CenterLayout.LayoutParams lp = (CenterLayout.LayoutParams) child.getLayoutParams(); + + childRight = lp.x + child.getMeasuredWidth(); + childBottom = lp.y + child.getMeasuredHeight(); + + maxWidth = Math.max(maxWidth, childRight); + maxHeight = Math.max(maxHeight, childBottom); + } + } + + maxWidth += mPaddingLeft + mPaddingRight; + maxHeight += mPaddingTop + mPaddingBottom; + + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), resolveSize(maxHeight, heightMeasureSpec)); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int count = getChildCount(); + mWidth = getMeasuredWidth(); + mHeight = getMeasuredHeight(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + CenterLayout.LayoutParams lp = (CenterLayout.LayoutParams) child.getLayoutParams(); + int childLeft = mPaddingLeft + lp.x; + if (lp.width > 0) + childLeft += (int) ((mWidth - lp.width) / 2.0); + else + childLeft += (int) ((mWidth - child.getMeasuredWidth()) / 2.0); + int childTop = mPaddingTop + lp.y; + if (lp.height > 0) + childTop += (int) ((mHeight - lp.height) / 2.0); + else + childTop += (int) ((mHeight - child.getMeasuredHeight()) / 2.0); + child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); + } + } + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof CenterLayout.LayoutParams; + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return new LayoutParams(p); + } + + public static class LayoutParams extends ViewGroup.LayoutParams { + public int x; + public int y; + + public LayoutParams(int width, int height, int x, int y) { + super(width, height); + this.x = x; + this.y = y; + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + } +} diff --git a/sample/src/main/java/tv/danmaku/ijk/media/widget/DebugLog.java b/sample/src/main/java/tv/danmaku/ijk/media/widget/DebugLog.java new file mode 100755 index 0000000..253eb8c --- /dev/null +++ b/sample/src/main/java/tv/danmaku/ijk/media/widget/DebugLog.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2013 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget; + +import java.util.Locale; +import android.util.Log; + +public class DebugLog { + public static final boolean ENABLE_ERROR = Pragma.ENABLE_VERBOSE; + public static final boolean ENABLE_INFO = Pragma.ENABLE_VERBOSE; + public static final boolean ENABLE_WARN = Pragma.ENABLE_VERBOSE; + public static final boolean ENABLE_DEBUG = Pragma.ENABLE_VERBOSE; + public static final boolean ENABLE_VERBOSE = Pragma.ENABLE_VERBOSE; + + public static int e(String tag, String msg) { + if (ENABLE_ERROR) { + return Log.e(tag, msg); + } + + return 0; + } + + public static int e(String tag, String msg, Throwable tr) { + if (ENABLE_ERROR) { + return Log.e(tag, msg, tr); + } + + return 0; + } + + public static int efmt(String tag, String fmt, Object... args) { + if (ENABLE_ERROR) { + String msg = String.format(Locale.US, fmt, args); + return Log.e(tag, msg); + } + + return 0; + } + + public static int i(String tag, String msg) { + if (ENABLE_INFO) { + return Log.i(tag, msg); + } + + return 0; + } + + public static int i(String tag, String msg, Throwable tr) { + if (ENABLE_INFO) { + return Log.i(tag, msg, tr); + } + + return 0; + } + + public static int ifmt(String tag, String fmt, Object... args) { + if (ENABLE_INFO) { + String msg = String.format(Locale.US, fmt, args); + return Log.i(tag, msg); + } + + return 0; + } + + public static int w(String tag, String msg) { + if (ENABLE_WARN) { + return Log.w(tag, msg); + } + + return 0; + } + + public static int w(String tag, String msg, Throwable tr) { + if (ENABLE_WARN) { + return Log.w(tag, msg, tr); + } + + return 0; + } + + public static int wfmt(String tag, String fmt, Object... args) { + if (ENABLE_WARN) { + String msg = String.format(Locale.US, fmt, args); + return Log.w(tag, msg); + } + + return 0; + } + + public static int d(String tag, String msg) { + if (ENABLE_DEBUG) { + return Log.d(tag, msg); + } + + return 0; + } + + public static int d(String tag, String msg, Throwable tr) { + if (ENABLE_DEBUG) { + return Log.d(tag, msg, tr); + } + + return 0; + } + + public static int dfmt(String tag, String fmt, Object... args) { + if (ENABLE_DEBUG) { + String msg = String.format(Locale.US, fmt, args); + return Log.d(tag, msg); + } + + return 0; + } + + public static int v(String tag, String msg) { + if (ENABLE_VERBOSE) { + return Log.v(tag, msg); + } + + return 0; + } + + public static int v(String tag, String msg, Throwable tr) { + if (ENABLE_VERBOSE) { + return Log.v(tag, msg, tr); + } + + return 0; + } + + public static int vfmt(String tag, String fmt, Object... args) { + if (ENABLE_VERBOSE) { + String msg = String.format(Locale.US, fmt, args); + return Log.v(tag, msg); + } + + return 0; + } + + public static void printStackTrace(Throwable e) { + if (ENABLE_WARN) { + e.printStackTrace(); + } + } + + public static void printCause(Throwable e) { + if (ENABLE_WARN) { + Throwable cause = e.getCause(); + if (cause != null) + e = cause; + + printStackTrace(e); + } + } +} diff --git a/sample/src/main/java/tv/danmaku/ijk/media/widget/MediaController.java b/sample/src/main/java/tv/danmaku/ijk/media/widget/MediaController.java new file mode 100755 index 0000000..04d428d --- /dev/null +++ b/sample/src/main/java/tv/danmaku/ijk/media/widget/MediaController.java @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * Copyright (C) 2012 YIXIA.COM + * Copyright (C) 2013 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget; + +import java.util.Locale; + +import tv.danmaku.ijk.media.sample.R; +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Rect; +import android.media.AudioManager; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.PopupWindow; +import android.widget.ProgressBar; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; + +/** + * A view containing controls for a MediaPlayer. Typically contains the buttons + * like "Play/Pause" and a progress slider. It takes care of synchronizing the + * controls with the state of the MediaPlayer. + *

+ * The way to use this class is to a) instantiate it programatically or b) + * create it in your xml layout. + *

+ * a) The MediaController will create a default set of controls and put them in + * a window floating above your application. Specifically, the controls will + * float above the view specified with setAnchorView(). By default, the window + * will disappear if left idle for three seconds and reappear when the user + * touches the anchor view. To customize the MediaController's style, layout and + * controls you should extend MediaController and override the {#link + * {@link #makeControllerView()} method. + *

+ * b) The MediaController is a FrameLayout, you can put it in your layout xml + * and get it through {@link #findViewById(int)}. + *

+ * NOTES: In each way, if you want customize the MediaController, + * the SeekBar's id must be {@link R.id#mediacontroller_progress}},
+ * the Play/Pause's must be {@link R.id#mediacontroller_pause},
+ * current time's must be {@link R.id#mediacontroller_time_current},
+ * total time's must be {@link R.id#mediacontroller_time_total},
+ * file name's must be {@link R.id#mediacontroller_file_name}.
+ * And your resources must have a pause_button drawable and a play_button drawable. + *

+ * Functions like show() and hide() have no effect when MediaController is + * created in an xml layout. + */ +public class MediaController extends FrameLayout { + private static final String TAG = MediaController.class.getSimpleName(); + + private MediaPlayerControl mPlayer; + private Context mContext; + private PopupWindow mWindow; + private int mAnimStyle; + private View mAnchor; + private View mRoot; + private ProgressBar mProgress; + private TextView mEndTime, mCurrentTime; + private TextView mFileName; + private OutlineTextView mInfoView; + private String mTitle; + private long mDuration; + private boolean mShowing; + private boolean mDragging; + private boolean mInstantSeeking = true; + private static final int sDefaultTimeout = 3000; + private static final int FADE_OUT = 1; + private static final int SHOW_PROGRESS = 2; + private boolean mFromXml = false; + private ImageButton mPauseButton; + + private AudioManager mAM; + + public MediaController(Context context, AttributeSet attrs) { + super(context, attrs); + mRoot = this; + mFromXml = true; + initController(context); + } + + public MediaController(Context context) { + super(context); + if (!mFromXml && initController(context)) + initFloatingWindow(); + } + + private boolean initController(Context context) { + mContext = context; + mAM = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + return true; + } + + @Override + public void onFinishInflate() { + super.onFinishInflate();//add super to suppress warning + if (mRoot != null) + initControllerView(mRoot); + } + + private void initFloatingWindow() { + mWindow = new PopupWindow(mContext); + mWindow.setFocusable(false); + mWindow.setBackgroundDrawable(null); + mWindow.setOutsideTouchable(true); + mAnimStyle = android.R.style.Animation; + } + + /** + * Set the view that acts as the anchor for the control view. This can for + * example be a VideoView, or your Activity's main view. + * + * @param view + * The view to which to anchor the controller when it is visible. + */ + public void setAnchorView(View view) { + mAnchor = view; + if (!mFromXml) { + removeAllViews(); + mRoot = makeControllerView(); + mWindow.setContentView(mRoot); + mWindow.setWidth(LayoutParams.MATCH_PARENT); + mWindow.setHeight(LayoutParams.WRAP_CONTENT); + } + initControllerView(mRoot); + } + + /** + * Create the view that holds the widgets that control playback. Derived + * classes can override this to create their own. + * + * @return The controller view. + */ + protected View makeControllerView() { + return ((LayoutInflater) mContext + .getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate( + R.layout.mediacontroller, this); + } + + private void initControllerView(View v) { + mPauseButton = (ImageButton) v + .findViewById(R.id.mediacontroller_pause); + if (mPauseButton != null) { + mPauseButton.requestFocus(); + mPauseButton.setOnClickListener(mPauseListener); + } + + mProgress = (SeekBar) v.findViewById(R.id.mediacontroller_progress); + if (mProgress != null) { + if (mProgress instanceof SeekBar) { + SeekBar seeker = (SeekBar) mProgress; + seeker.setOnSeekBarChangeListener(mSeekListener); + seeker.setThumbOffset(1); + } + mProgress.setMax(1000); + } + + mEndTime = (TextView) v.findViewById(R.id.mediacontroller_time_total); + mCurrentTime = (TextView) v + .findViewById(R.id.mediacontroller_time_current); + mFileName = (TextView) v.findViewById(R.id.mediacontroller_file_name); + if (mFileName != null) + mFileName.setText(mTitle); + } + + public void setMediaPlayer(MediaPlayerControl player) { + mPlayer = player; + updatePausePlay(); + } + + /** + * Control the action when the seekbar dragged by user + * + * @param seekWhenDragging + * True the media will seek periodically + */ + public void setInstantSeeking(boolean seekWhenDragging) { + mInstantSeeking = seekWhenDragging; + } + + public void show() { + show(sDefaultTimeout); + } + + /** + * Set the content of the file_name TextView + * + * @param name + */ + public void setFileName(String name) { + mTitle = name; + if (mFileName != null) + mFileName.setText(mTitle); + } + + /** + * Set the View to hold some information when interact with the + * MediaController + * + * @param v + */ + public void setInfoView(OutlineTextView v) { + mInfoView = v; + } + + private void disableUnsupportedButtons() { + try { + if (mPauseButton != null && !mPlayer.canPause()) + mPauseButton.setEnabled(false); + } catch (IncompatibleClassChangeError ex) { + } + } + + /** + *

+ * Change the animation style resource for this controller. + *

+ * + *

+ * If the controller is showing, calling this method will take effect only + * the next time the controller is shown. + *

+ * + * @param animationStyle + * animation style to use when the controller appears and + * disappears. Set to -1 for the default animation, 0 for no + * animation, or a resource identifier for an explicit animation. + * + */ + public void setAnimationStyle(int animationStyle) { + mAnimStyle = animationStyle; + } + + /** + * Show the controller on screen. It will go away automatically after + * 'timeout' milliseconds of inactivity. + * + * @param timeout + * The timeout in milliseconds. Use 0 to show the controller + * until hide() is called. + */ + @SuppressLint("InlinedApi") + public void show(int timeout) { + if (!mShowing && mAnchor != null && mAnchor.getWindowToken() != null) { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH){ + mAnchor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + } + if (mPauseButton != null) + mPauseButton.requestFocus(); + disableUnsupportedButtons(); + + if (mFromXml) { + setVisibility(View.VISIBLE); + } else { + int[] location = new int[2]; + + mAnchor.getLocationOnScreen(location); + Rect anchorRect = new Rect(location[0], location[1], + location[0] + mAnchor.getWidth(), location[1] + + mAnchor.getHeight()); + + mWindow.setAnimationStyle(mAnimStyle); + mWindow.showAtLocation(mAnchor, Gravity.BOTTOM, + anchorRect.left, 0); + } + mShowing = true; + if (mShownListener != null) + mShownListener.onShown(); + } + updatePausePlay(); + mHandler.sendEmptyMessage(SHOW_PROGRESS); + + if (timeout != 0) { + mHandler.removeMessages(FADE_OUT); + mHandler.sendMessageDelayed(mHandler.obtainMessage(FADE_OUT), + timeout); + } + } + + public boolean isShowing() { + return mShowing; + } + + @SuppressLint("InlinedApi") + public void hide() { + if (mAnchor == null) + return; + + if (mShowing) { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH){ + mAnchor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + } + try { + mHandler.removeMessages(SHOW_PROGRESS); + if (mFromXml) + setVisibility(View.GONE); + else + mWindow.dismiss(); + } catch (IllegalArgumentException ex) { + DebugLog.d(TAG, "MediaController already removed"); + } + mShowing = false; + if (mHiddenListener != null) + mHiddenListener.onHidden(); + } + } + + public interface OnShownListener { + public void onShown(); + } + + private OnShownListener mShownListener; + + public void setOnShownListener(OnShownListener l) { + mShownListener = l; + } + + public interface OnHiddenListener { + public void onHidden(); + } + + private OnHiddenListener mHiddenListener; + + public void setOnHiddenListener(OnHiddenListener l) { + mHiddenListener = l; + } + + @SuppressLint("HandlerLeak") + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + long pos; + switch (msg.what) { + case FADE_OUT: + hide(); + break; + case SHOW_PROGRESS: + pos = setProgress(); + if (!mDragging && mShowing) { + msg = obtainMessage(SHOW_PROGRESS); + sendMessageDelayed(msg, 1000 - (pos % 1000)); + updatePausePlay(); + } + break; + } + } + }; + + private long setProgress() { + if (mPlayer == null || mDragging) + return 0; + + int position = mPlayer.getCurrentPosition(); + int duration = mPlayer.getDuration(); + if (mProgress != null) { + if (duration > 0) { + long pos = 1000L * position / duration; + mProgress.setProgress((int) pos); + } + int percent = mPlayer.getBufferPercentage(); + mProgress.setSecondaryProgress(percent * 10); + } + + mDuration = duration; + + if (mEndTime != null) + mEndTime.setText(generateTime(mDuration)); + if (mCurrentTime != null) + mCurrentTime.setText(generateTime(position)); + + return position; + } + + private static String generateTime(long position) { + int totalSeconds = (int) ((position / 1000.0)+0.5); + + int seconds = totalSeconds % 60; + int minutes = (totalSeconds / 60) % 60; + int hours = totalSeconds / 3600; + + if (hours > 0) { + return String.format(Locale.US, "%02d:%02d:%02d", hours, minutes, + seconds).toString(); + } else { + return String.format(Locale.US, "%02d:%02d", minutes, seconds) + .toString(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + show(sDefaultTimeout); + return true; + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + show(sDefaultTimeout); + return false; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + int keyCode = event.getKeyCode(); + if (event.getRepeatCount() == 0 + && (keyCode == KeyEvent.KEYCODE_HEADSETHOOK + || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_SPACE)) { + doPauseResume(); + show(sDefaultTimeout); + if (mPauseButton != null) + mPauseButton.requestFocus(); + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP) { + if (mPlayer.isPlaying()) { + mPlayer.pause(); + updatePausePlay(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_BACK + || keyCode == KeyEvent.KEYCODE_MENU) { + hide(); + return true; + } else { + show(sDefaultTimeout); + } + return super.dispatchKeyEvent(event); + } + + private View.OnClickListener mPauseListener = new View.OnClickListener() { + public void onClick(View v) { + doPauseResume(); + show(sDefaultTimeout); + } + }; + + private void updatePausePlay() { + if (mRoot == null || mPauseButton == null) + return; + + if (mPlayer.isPlaying()) + mPauseButton + .setImageResource(R.drawable.mediacontroller_pause_button); + else + mPauseButton + .setImageResource(R.drawable.mediacontroller_play_button); + } + + private void doPauseResume() { + if (mPlayer.isPlaying()) + mPlayer.pause(); + else + mPlayer.start(); + updatePausePlay(); + } + + private Runnable lastRunnable; + private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { + public void onStartTrackingTouch(SeekBar bar) { + mDragging = true; + show(3600000); + mHandler.removeMessages(SHOW_PROGRESS); + if (mInstantSeeking) + mAM.setStreamMute(AudioManager.STREAM_MUSIC, true); + if (mInfoView != null) { + mInfoView.setText(""); + mInfoView.setVisibility(View.VISIBLE); + } + } + + public void onProgressChanged(SeekBar bar, int progress, + boolean fromuser) { + if (!fromuser) + return; + + final long newposition = (mDuration * progress) / 1000; + String time = generateTime(newposition); + if (mInstantSeeking) { + mHandler.removeCallbacks(lastRunnable); + lastRunnable = new Runnable() { + @Override + public void run() { + mPlayer.seekTo(newposition); + } + }; + mHandler.postDelayed(lastRunnable, 200); + } + if (mInfoView != null) + mInfoView.setText(time); + if (mCurrentTime != null) + mCurrentTime.setText(time); + } + + public void onStopTrackingTouch(SeekBar bar) { + if (!mInstantSeeking) + mPlayer.seekTo((mDuration * bar.getProgress()) / 1000); + if (mInfoView != null) { + mInfoView.setText(""); + mInfoView.setVisibility(View.GONE); + } + show(sDefaultTimeout); + mHandler.removeMessages(SHOW_PROGRESS); + mAM.setStreamMute(AudioManager.STREAM_MUSIC, false); + mDragging = false; + mHandler.sendEmptyMessageDelayed(SHOW_PROGRESS, 1000); + } + }; + + @Override + public void setEnabled(boolean enabled) { + if (mPauseButton != null) + mPauseButton.setEnabled(enabled); + if (mProgress != null) + mProgress.setEnabled(enabled); + disableUnsupportedButtons(); + super.setEnabled(enabled); + } + + public interface MediaPlayerControl { + void start(); + + void pause(); + + int getDuration(); + + int getCurrentPosition(); + + void seekTo(long pos); + + boolean isPlaying(); + + int getBufferPercentage(); + + boolean canPause(); + + boolean canSeekBackward(); + + boolean canSeekForward(); + } + +} diff --git a/sample/src/main/java/tv/danmaku/ijk/media/widget/OutlineTextView.java b/sample/src/main/java/tv/danmaku/ijk/media/widget/OutlineTextView.java new file mode 100755 index 0000000..98379bb --- /dev/null +++ b/sample/src/main/java/tv/danmaku/ijk/media/widget/OutlineTextView.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2011 Cedric Fung (wolfplanet@gmail.com) + * Copyright (C) 2013 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.widget.TextView; + +/** + * Display text with border, use the same XML attrs as + * {@link android.widget.TextView}, except that {@link OutlineTextView} will + * transform the shadow to border + */ +@SuppressLint("DrawAllocation") +public class OutlineTextView extends TextView { + public OutlineTextView(Context context) { + super(context); + initPaint(); + } + + public OutlineTextView(Context context, AttributeSet attrs) { + super(context, attrs); + initPaint(); + } + + public OutlineTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initPaint(); + } + + private void initPaint() { + mTextPaint = new TextPaint(); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(getTextSize()); + mTextPaint.setColor(mColor); + mTextPaint.setStyle(Paint.Style.FILL); + mTextPaint.setTypeface(getTypeface()); + + mTextPaintOutline = new TextPaint(); + mTextPaintOutline.setAntiAlias(true); + mTextPaintOutline.setTextSize(getTextSize()); + mTextPaintOutline.setColor(mBorderColor); + mTextPaintOutline.setStyle(Paint.Style.STROKE); + mTextPaintOutline.setTypeface(getTypeface()); + mTextPaintOutline.setStrokeWidth(mBorderSize); + } + + public void setText(String text) { + super.setText(text); + mText = text.toString(); + requestLayout(); + invalidate(); + } + + public void setTextSize(float size) { + super.setTextSize(size); + requestLayout(); + invalidate(); + initPaint(); + } + + public void setTextColor(int color) { + super.setTextColor(color); + mColor = color; + invalidate(); + initPaint(); + } + + public void setShadowLayer(float radius, float dx, float dy, int color) { + super.setShadowLayer(radius, dx, dy, color); + mBorderSize = radius; + mBorderColor = color; + requestLayout(); + invalidate(); + initPaint(); + } + + public void setTypeface(Typeface tf, int style) { + super.setTypeface(tf, style); + requestLayout(); + invalidate(); + initPaint(); + } + + public void setTypeface(Typeface tf) { + super.setTypeface(tf); + requestLayout(); + invalidate(); + initPaint(); + } + + @Override + protected void onDraw(Canvas canvas) { + Layout layout = new StaticLayout(getText(), mTextPaintOutline, + getWidth(), Layout.Alignment.ALIGN_CENTER, mSpacingMult, + mSpacingAdd, mIncludePad); + layout.draw(canvas); + layout = new StaticLayout(getText(), mTextPaint, getWidth(), + Layout.Alignment.ALIGN_CENTER, mSpacingMult, mSpacingAdd, + mIncludePad); + layout.draw(canvas); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Layout layout = new StaticLayout(getText(), mTextPaintOutline, + measureWidth(widthMeasureSpec), Layout.Alignment.ALIGN_CENTER, + mSpacingMult, mSpacingAdd, mIncludePad); + int ex = (int) (mBorderSize * 2 + 1); + setMeasuredDimension(measureWidth(widthMeasureSpec) + ex, + measureHeight(heightMeasureSpec) * layout.getLineCount() + ex); + } + + private int measureWidth(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + result = specSize; + } else { + result = (int) mTextPaintOutline.measureText(mText) + + getPaddingLeft() + getPaddingRight(); + if (specMode == MeasureSpec.AT_MOST) { + result = Math.min(result, specSize); + } + } + + return result; + } + + private int measureHeight(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + mAscent = (int) mTextPaintOutline.ascent(); + if (specMode == MeasureSpec.EXACTLY) { + result = specSize; + } else { + result = (int) (-mAscent + mTextPaintOutline.descent()) + + getPaddingTop() + getPaddingBottom(); + if (specMode == MeasureSpec.AT_MOST) { + result = Math.min(result, specSize); + } + } + return result; + } + + private TextPaint mTextPaint; + private TextPaint mTextPaintOutline; + private String mText = ""; + private int mAscent = 0; + private float mBorderSize; + private int mBorderColor; + private int mColor; + private float mSpacingMult = 1.0f; + private float mSpacingAdd = 0; + private boolean mIncludePad = true; +} \ No newline at end of file diff --git a/sample/src/main/java/tv/danmaku/ijk/media/widget/Pragma.java b/sample/src/main/java/tv/danmaku/ijk/media/widget/Pragma.java new file mode 100755 index 0000000..0eb7a4e --- /dev/null +++ b/sample/src/main/java/tv/danmaku/ijk/media/widget/Pragma.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget; + +/*- + * configurated by app project + */ +public class Pragma { + public static final boolean ENABLE_VERBOSE = tv.danmaku.ijk.media.player.pragma.Pragma.ENABLE_VERBOSE; +} diff --git a/sample/src/main/java/tv/danmaku/ijk/media/widget/ScreenResolution.java b/sample/src/main/java/tv/danmaku/ijk/media/widget/ScreenResolution.java new file mode 100755 index 0000000..42cf5ba --- /dev/null +++ b/sample/src/main/java/tv/danmaku/ijk/media/widget/ScreenResolution.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * Copyright (C) 2013 YIXIA.COM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tv.danmaku.ijk.media.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.DisplayMetrics; +import android.util.Pair; +import android.view.Display; +import android.view.WindowManager; + +import java.lang.reflect.Method; + +/** + * Class to get the real screen resolution includes the system status bar. + * We can get the value by calling the getRealMetrics method if API >= 17 + * Reflection needed on old devices.. + * */ +public class ScreenResolution { + /** + * Gets the resolution, + * @return a pair to return the width and height + * */ + public static Pair getResolution(Context ctx){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return getRealResolution(ctx); + } + else { + return getRealResolutionOnOldDevice(ctx); + } + } + + /** + * Gets resolution on old devices. + * Tries the reflection to get the real resolution first. + * Fall back to getDisplayMetrics if the above method failed. + * */ + private static Pair getRealResolutionOnOldDevice(Context ctx) { + try{ + WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + Method mGetRawWidth = Display.class.getMethod("getRawWidth"); + Method mGetRawHeight = Display.class.getMethod("getRawHeight"); + Integer realWidth = (Integer) mGetRawWidth.invoke(display); + Integer realHeight = (Integer) mGetRawHeight.invoke(display); + return new Pair(realWidth, realHeight); + } + catch (Exception e) { + DisplayMetrics disp = ctx.getResources().getDisplayMetrics(); + return new Pair(disp.widthPixels, disp.heightPixels); + } + } + + /** + * Gets real resolution via the new getRealMetrics API. + * */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + private static Pair getRealResolution(Context ctx) { + WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getRealMetrics(metrics); + return new Pair(metrics.widthPixels, metrics.heightPixels); + } +} \ No newline at end of file diff --git a/sample/src/main/java/tv/danmaku/ijk/media/widget/VideoView.java b/sample/src/main/java/tv/danmaku/ijk/media/widget/VideoView.java new file mode 100755 index 0000000..85778a2 --- /dev/null +++ b/sample/src/main/java/tv/danmaku/ijk/media/widget/VideoView.java @@ -0,0 +1,688 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * Copyright (C) 2012 YIXIA.COM + * Copyright (C) 2013 Zhang Rui + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget; + +import java.io.IOException; +import java.util.List; + +import tv.danmaku.ijk.media.player.IMediaPlayer; +import tv.danmaku.ijk.media.player.IMediaPlayer.OnBufferingUpdateListener; +import tv.danmaku.ijk.media.player.IMediaPlayer.OnCompletionListener; +import tv.danmaku.ijk.media.player.IMediaPlayer.OnErrorListener; +import tv.danmaku.ijk.media.player.IMediaPlayer.OnInfoListener; +import tv.danmaku.ijk.media.player.IMediaPlayer.OnPreparedListener; +import tv.danmaku.ijk.media.player.IMediaPlayer.OnSeekCompleteListener; +import tv.danmaku.ijk.media.player.IMediaPlayer.OnVideoSizeChangedListener; +import tv.danmaku.ijk.media.player.IjkMediaPlayer; +import tv.danmaku.ijk.media.player.option.AvFourCC; +import tv.danmaku.ijk.media.player.option.format.AvFormatOption_HttpDetectRangeSupport; +import tv.danmaku.ijk.media.sample.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.media.AudioManager; +import android.net.Uri; +import android.util.AttributeSet; +import android.util.Pair; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup.LayoutParams; + +/** + * Displays a video file. The VideoView class can load images from various + * sources (such as resources or content providers), takes care of computing its + * measurement from the video so that it can be used in any layout manager, and + * provides various display options such as scaling and tinting. + * + * VideoView also provide many wrapper methods for + * {@link io.vov.vitamio.MediaPlayer}, such as {@link #getVideoWidth()}, + * {@link #setSubShown(boolean)} + */ +public class VideoView extends SurfaceView implements + MediaController.MediaPlayerControl { + private static final String TAG = VideoView.class.getName(); + + private Uri mUri; + private long mDuration; + private String mUserAgent; + + private static final int STATE_ERROR = -1; + private static final int STATE_IDLE = 0; + private static final int STATE_PREPARING = 1; + private static final int STATE_PREPARED = 2; + private static final int STATE_PLAYING = 3; + private static final int STATE_PAUSED = 4; + private static final int STATE_PLAYBACK_COMPLETED = 5; + private static final int STATE_SUSPEND = 6; + private static final int STATE_RESUME = 7; + private static final int STATE_SUSPEND_UNSUPPORTED = 8; + + private int mCurrentState = STATE_IDLE; + private int mTargetState = STATE_IDLE; + + private int mVideoLayout = VIDEO_LAYOUT_SCALE; + public static final int VIDEO_LAYOUT_ORIGIN = 0; + public static final int VIDEO_LAYOUT_SCALE = 1; + public static final int VIDEO_LAYOUT_STRETCH = 2; + public static final int VIDEO_LAYOUT_ZOOM = 3; + + private SurfaceHolder mSurfaceHolder = null; + private IMediaPlayer mMediaPlayer = null; + private int mVideoWidth; + private int mVideoHeight; + private int mVideoSarNum; + private int mVideoSarDen; + private int mSurfaceWidth; + private int mSurfaceHeight; + private MediaController mMediaController; + private View mMediaBufferingIndicator; + private OnCompletionListener mOnCompletionListener; + private OnPreparedListener mOnPreparedListener; + private OnErrorListener mOnErrorListener; + private OnSeekCompleteListener mOnSeekCompleteListener; + private OnInfoListener mOnInfoListener; + private OnBufferingUpdateListener mOnBufferingUpdateListener; + private int mCurrentBufferPercentage; + private long mSeekWhenPrepared; + private boolean mCanPause = true; + private boolean mCanSeekBack = true; + private boolean mCanSeekForward = true; + private Context mContext; + + public VideoView(Context context) { + super(context); + initVideoView(context); + } + + public VideoView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public VideoView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initVideoView(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = getDefaultSize(mVideoWidth, widthMeasureSpec); + int height = getDefaultSize(mVideoHeight, heightMeasureSpec); + setMeasuredDimension(width, height); + } + + /** + * Set the display options + * + * @param layout + *
    + *
  • {@link #VIDEO_LAYOUT_ORIGIN} + *
  • {@link #VIDEO_LAYOUT_SCALE} + *
  • {@link #VIDEO_LAYOUT_STRETCH} + *
  • {@link #VIDEO_LAYOUT_ZOOM} + *
+ * @param aspectRatio + * video aspect ratio, will audo detect if 0. + */ + public void setVideoLayout(int layout) { + LayoutParams lp = getLayoutParams(); + Pair res = ScreenResolution.getResolution(mContext); + int windowWidth = res.first.intValue(), windowHeight = res.second.intValue(); + float windowRatio = windowWidth / (float) windowHeight; + int sarNum = mVideoSarNum; + int sarDen = mVideoSarDen; + if (mVideoHeight > 0 && mVideoWidth > 0) { + float videoRatio = ((float) (mVideoWidth)) / mVideoHeight; + if (sarNum > 0 && sarDen > 0) + videoRatio = videoRatio * sarNum / sarDen; + mSurfaceHeight = mVideoHeight; + mSurfaceWidth = mVideoWidth; + + if (VIDEO_LAYOUT_ORIGIN == layout && mSurfaceWidth < windowWidth + && mSurfaceHeight < windowHeight) { + lp.width = (int) (mSurfaceHeight * videoRatio); + lp.height = mSurfaceHeight; + } else if (layout == VIDEO_LAYOUT_ZOOM) { + lp.width = windowRatio > videoRatio ? windowWidth + : (int) (videoRatio * windowHeight); + lp.height = windowRatio < videoRatio ? windowHeight + : (int) (windowWidth / videoRatio); + } else { + boolean full = layout == VIDEO_LAYOUT_STRETCH; + lp.width = (full || windowRatio < videoRatio) ? windowWidth + : (int) (videoRatio * windowHeight); + lp.height = (full || windowRatio > videoRatio) ? windowHeight + : (int) (windowWidth / videoRatio); + } + setLayoutParams(lp); + getHolder().setFixedSize(mSurfaceWidth, mSurfaceHeight); + DebugLog.dfmt( + TAG, + "VIDEO: %dx%dx%f[SAR:%d:%d], Surface: %dx%d, LP: %dx%d, Window: %dx%dx%f", + mVideoWidth, mVideoHeight, videoRatio, mVideoSarNum, + mVideoSarDen, mSurfaceWidth, mSurfaceHeight, lp.width, + lp.height, windowWidth, windowHeight, windowRatio); + } + mVideoLayout = layout; + } + + private void initVideoView(Context ctx) { + mContext = ctx; + mVideoWidth = 0; + mVideoHeight = 0; + mVideoSarNum = 0; + mVideoSarDen = 0; + getHolder().addCallback(mSHCallback); + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + if (ctx instanceof Activity) + ((Activity) ctx).setVolumeControlStream(AudioManager.STREAM_MUSIC); + } + + public boolean isValid() { + return (mSurfaceHolder != null && mSurfaceHolder.getSurface().isValid()); + } + + public void setVideoPath(String path) { + setVideoURI(Uri.parse(path)); + } + + public void setVideoURI(Uri uri) { + mUri = uri; + mSeekWhenPrepared = 0; + openVideo(); + requestLayout(); + invalidate(); + } + + public void setUserAgent(String ua) { + mUserAgent = ua; + } + + public void stopPlayback() { + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + } + } + + private void openVideo() { + if (mUri == null || mSurfaceHolder == null) + return; + + Intent i = new Intent("com.android.music.musicservicecommand"); + i.putExtra("command", "pause"); + mContext.sendBroadcast(i); + + release(false); + try { + mDuration = -1; + mCurrentBufferPercentage = 0; + IjkMediaPlayer ijkMediaPlayer = null; + if (mUri != null) { + ijkMediaPlayer = new IjkMediaPlayer(); + + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32); + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 12); + + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0); + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "user_agent", mUserAgent); + + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48); + } + mMediaPlayer = ijkMediaPlayer; + mMediaPlayer.setOnPreparedListener(mPreparedListener); + mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); + mMediaPlayer.setOnCompletionListener(mCompletionListener); + mMediaPlayer.setOnErrorListener(mErrorListener); + mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); + mMediaPlayer.setOnInfoListener(mInfoListener); + mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener); + if (mUri != null) + mMediaPlayer.setDataSource(mUri.toString()); + mMediaPlayer.setDisplay(mSurfaceHolder); + mMediaPlayer.setScreenOnWhilePlaying(true); + mMediaPlayer.prepareAsync(); + mCurrentState = STATE_PREPARING; + attachMediaController(); + } catch (IOException ex) { + DebugLog.e(TAG, "Unable to open content: " + mUri, ex); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + mErrorListener.onError(mMediaPlayer, + IMediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } catch (IllegalArgumentException ex) { + DebugLog.e(TAG, "Unable to open content: " + mUri, ex); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + mErrorListener.onError(mMediaPlayer, + IMediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } + } + + public void setMediaController(MediaController controller) { + if (mMediaController != null) + mMediaController.hide(); + mMediaController = controller; + attachMediaController(); + } + + public void setMediaBufferingIndicator(View mediaBufferingIndicator) { + if (mMediaBufferingIndicator != null) + mMediaBufferingIndicator.setVisibility(View.GONE); + mMediaBufferingIndicator = mediaBufferingIndicator; + } + + private void attachMediaController() { + if (mMediaPlayer != null && mMediaController != null) { + mMediaController.setMediaPlayer(this); + View anchorView = this.getParent() instanceof View ? (View) this + .getParent() : this; + mMediaController.setAnchorView(anchorView); + mMediaController.setEnabled(isInPlaybackState()); + + if (mUri != null) { + List paths = mUri.getPathSegments(); + String name = paths == null || paths.isEmpty() ? "null" : paths + .get(paths.size() - 1); + mMediaController.setFileName(name); + } + } + } + + OnVideoSizeChangedListener mSizeChangedListener = new OnVideoSizeChangedListener() { + public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, + int sarNum, int sarDen) { + DebugLog.dfmt(TAG, "onVideoSizeChanged: (%dx%d)", width, height); + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + mVideoSarNum = sarNum; + mVideoSarDen = sarDen; + if (mVideoWidth != 0 && mVideoHeight != 0) + setVideoLayout(mVideoLayout); + } + }; + + OnPreparedListener mPreparedListener = new OnPreparedListener() { + public void onPrepared(IMediaPlayer mp) { + DebugLog.d(TAG, "onPrepared"); + mCurrentState = STATE_PREPARED; + mTargetState = STATE_PLAYING; + + if (mOnPreparedListener != null) + mOnPreparedListener.onPrepared(mMediaPlayer); + if (mMediaController != null) + mMediaController.setEnabled(true); + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + + long seekToPosition = mSeekWhenPrepared; + + if (seekToPosition != 0) + seekTo(seekToPosition); + if (mVideoWidth != 0 && mVideoHeight != 0) { + setVideoLayout(mVideoLayout); + if (mSurfaceWidth == mVideoWidth + && mSurfaceHeight == mVideoHeight) { + if (mTargetState == STATE_PLAYING) { + start(); + if (mMediaController != null) + mMediaController.show(); + } else if (!isPlaying() + && (seekToPosition != 0 || getCurrentPosition() > 0)) { + if (mMediaController != null) + mMediaController.show(0); + } + } + } else if (mTargetState == STATE_PLAYING) { + start(); + } + } + }; + + private OnCompletionListener mCompletionListener = new OnCompletionListener() { + public void onCompletion(IMediaPlayer mp) { + DebugLog.d(TAG, "onCompletion"); + mCurrentState = STATE_PLAYBACK_COMPLETED; + mTargetState = STATE_PLAYBACK_COMPLETED; + if (mMediaController != null) + mMediaController.hide(); + if (mOnCompletionListener != null) + mOnCompletionListener.onCompletion(mMediaPlayer); + } + }; + + private OnErrorListener mErrorListener = new OnErrorListener() { + public boolean onError(IMediaPlayer mp, int framework_err, int impl_err) { + DebugLog.dfmt(TAG, "Error: %d, %d", framework_err, impl_err); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + if (mMediaController != null) + mMediaController.hide(); + + if (mOnErrorListener != null) { + if (mOnErrorListener.onError(mMediaPlayer, framework_err, + impl_err)) + return true; + } + + if (getWindowToken() != null) { + int message = framework_err == IMediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK ? R.string.vitamio_videoview_error_text_invalid_progressive_playback + : R.string.vitamio_videoview_error_text_unknown; + + new AlertDialog.Builder(mContext) + .setTitle(R.string.vitamio_videoview_error_title) + .setMessage(message) + .setPositiveButton( + R.string.vitamio_videoview_error_button, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int whichButton) { + if (mOnCompletionListener != null) + mOnCompletionListener + .onCompletion(mMediaPlayer); + } + }).setCancelable(false).show(); + } + return true; + } + }; + + private OnBufferingUpdateListener mBufferingUpdateListener = new OnBufferingUpdateListener() { + public void onBufferingUpdate(IMediaPlayer mp, int percent) { + mCurrentBufferPercentage = percent; + if (mOnBufferingUpdateListener != null) + mOnBufferingUpdateListener.onBufferingUpdate(mp, percent); + } + }; + + private OnInfoListener mInfoListener = new OnInfoListener() { + @Override + public boolean onInfo(IMediaPlayer mp, int what, int extra) { + DebugLog.dfmt(TAG, "onInfo: (%d, %d)", what, extra); + if (mOnInfoListener != null) { + mOnInfoListener.onInfo(mp, what, extra); + } else if (mMediaPlayer != null) { + if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_START) { + DebugLog.dfmt(TAG, "onInfo: (MEDIA_INFO_BUFFERING_START)"); + if (mMediaBufferingIndicator != null) + mMediaBufferingIndicator.setVisibility(View.VISIBLE); + } else if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_END) { + DebugLog.dfmt(TAG, "onInfo: (MEDIA_INFO_BUFFERING_END)"); + if (mMediaBufferingIndicator != null) + mMediaBufferingIndicator.setVisibility(View.GONE); + } + } + + return true; + } + }; + + private OnSeekCompleteListener mSeekCompleteListener = new OnSeekCompleteListener() { + @Override + public void onSeekComplete(IMediaPlayer mp) { + DebugLog.d(TAG, "onSeekComplete"); + if (mOnSeekCompleteListener != null) + mOnSeekCompleteListener.onSeekComplete(mp); + } + }; + + public void setOnPreparedListener(OnPreparedListener l) { + mOnPreparedListener = l; + } + + public void setOnCompletionListener(OnCompletionListener l) { + mOnCompletionListener = l; + } + + public void setOnErrorListener(OnErrorListener l) { + mOnErrorListener = l; + } + + public void setOnBufferingUpdateListener(OnBufferingUpdateListener l) { + mOnBufferingUpdateListener = l; + } + + public void setOnSeekCompleteListener(OnSeekCompleteListener l) { + mOnSeekCompleteListener = l; + } + + public void setOnInfoListener(OnInfoListener l) { + mOnInfoListener = l; + } + + SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() { + public void surfaceChanged(SurfaceHolder holder, int format, int w, + int h) { + mSurfaceHolder = holder; + if (mMediaPlayer != null) { + mMediaPlayer.setDisplay(mSurfaceHolder); + } + + mSurfaceWidth = w; + mSurfaceHeight = h; + boolean isValidState = (mTargetState == STATE_PLAYING); + boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h); + if (mMediaPlayer != null && isValidState && hasValidSize) { + if (mSeekWhenPrepared != 0) + seekTo(mSeekWhenPrepared); + start(); + if (mMediaController != null) { + if (mMediaController.isShowing()) + mMediaController.hide(); + mMediaController.show(); + } + } + } + + public void surfaceCreated(SurfaceHolder holder) { + mSurfaceHolder = holder; + if (mMediaPlayer != null && mCurrentState == STATE_SUSPEND + && mTargetState == STATE_RESUME) { + mMediaPlayer.setDisplay(mSurfaceHolder); + resume(); + } else { + openVideo(); + } + } + + public void surfaceDestroyed(SurfaceHolder holder) { + mSurfaceHolder = null; + if (mMediaController != null) + mMediaController.hide(); + if (mCurrentState != STATE_SUSPEND) + release(true); + } + }; + + private void release(boolean cleartargetstate) { + if (mMediaPlayer != null) { + mMediaPlayer.reset(); + mMediaPlayer.release(); + mMediaPlayer = null; + mCurrentState = STATE_IDLE; + if (cleartargetstate) + mTargetState = STATE_IDLE; + } + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (isInPlaybackState() && mMediaController != null) + toggleMediaControlsVisiblity(); + return false; + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + if (isInPlaybackState() && mMediaController != null) + toggleMediaControlsVisiblity(); + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK + && keyCode != KeyEvent.KEYCODE_VOLUME_UP + && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN + && keyCode != KeyEvent.KEYCODE_MENU + && keyCode != KeyEvent.KEYCODE_CALL + && keyCode != KeyEvent.KEYCODE_ENDCALL; + if (isInPlaybackState() && isKeyCodeSupported + && mMediaController != null) { + if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK + || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE + || keyCode == KeyEvent.KEYCODE_SPACE) { + if (mMediaPlayer.isPlaying()) { + pause(); + mMediaController.show(); + } else { + start(); + mMediaController.hide(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP + && mMediaPlayer.isPlaying()) { + pause(); + mMediaController.show(); + } else { + toggleMediaControlsVisiblity(); + } + } + + return super.onKeyDown(keyCode, event); + } + + private void toggleMediaControlsVisiblity() { + if (mMediaController.isShowing()) { + mMediaController.hide(); + } else { + mMediaController.show(); + } + } + + @Override + public void start() { + if (isInPlaybackState()) { + mMediaPlayer.start(); + mCurrentState = STATE_PLAYING; + } + mTargetState = STATE_PLAYING; + } + + @Override + public void pause() { + if (isInPlaybackState()) { + if (mMediaPlayer.isPlaying()) { + mMediaPlayer.pause(); + mCurrentState = STATE_PAUSED; + } + } + mTargetState = STATE_PAUSED; + } + + public void resume() { + if (mSurfaceHolder == null && mCurrentState == STATE_SUSPEND) { + mTargetState = STATE_RESUME; + } else if (mCurrentState == STATE_SUSPEND_UNSUPPORTED) { + openVideo(); + } + } + + @Override + public int getDuration() { + if (isInPlaybackState()) { + if (mDuration > 0) + return (int) mDuration; + mDuration = mMediaPlayer.getDuration(); + return (int) mDuration; + } + mDuration = -1; + return (int) mDuration; + } + + @Override + public int getCurrentPosition() { + if (isInPlaybackState()) { + long position = mMediaPlayer.getCurrentPosition(); + return (int) position; + } + return 0; + } + + @Override + public void seekTo(long msec) { + if (isInPlaybackState()) { + mMediaPlayer.seekTo(msec); + mSeekWhenPrepared = 0; + } else { + mSeekWhenPrepared = msec; + } + } + + @Override + public boolean isPlaying() { + return isInPlaybackState() && mMediaPlayer.isPlaying(); + } + + @Override + public int getBufferPercentage() { + if (mMediaPlayer != null) + return mCurrentBufferPercentage; + return 0; + } + + public int getVideoWidth() { + return mVideoWidth; + } + + public int getVideoHeight() { + return mVideoHeight; + } + + protected boolean isInPlaybackState() { + return (mMediaPlayer != null && mCurrentState != STATE_ERROR + && mCurrentState != STATE_IDLE && mCurrentState != STATE_PREPARING); + } + + public boolean canPause() { + return mCanPause; + } + + public boolean canSeekBackward() { + return mCanSeekBack; + } + + public boolean canSeekForward() { + return mCanSeekForward; + } +} diff --git a/sample/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so b/sample/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so new file mode 100755 index 0000000..951f5ed Binary files /dev/null and b/sample/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so differ diff --git a/sample/src/main/jniLibs/armeabi-v7a/libijkplayer.so b/sample/src/main/jniLibs/armeabi-v7a/libijkplayer.so new file mode 100755 index 0000000..31e3bee Binary files /dev/null and b/sample/src/main/jniLibs/armeabi-v7a/libijkplayer.so differ diff --git a/sample/src/main/jniLibs/armeabi-v7a/libijksdl.so b/sample/src/main/jniLibs/armeabi-v7a/libijksdl.so new file mode 100755 index 0000000..7b7b31a Binary files /dev/null and b/sample/src/main/jniLibs/armeabi-v7a/libijksdl.so differ diff --git a/sample/src/main/jniLibs/armeabi/libijkffmpeg.so b/sample/src/main/jniLibs/armeabi/libijkffmpeg.so new file mode 100755 index 0000000..d37b652 Binary files /dev/null and b/sample/src/main/jniLibs/armeabi/libijkffmpeg.so differ diff --git a/sample/src/main/jniLibs/armeabi/libijkplayer.so b/sample/src/main/jniLibs/armeabi/libijkplayer.so new file mode 100755 index 0000000..4c400e1 Binary files /dev/null and b/sample/src/main/jniLibs/armeabi/libijkplayer.so differ diff --git a/sample/src/main/jniLibs/armeabi/libijksdl.so b/sample/src/main/jniLibs/armeabi/libijksdl.so new file mode 100755 index 0000000..79c8c8a Binary files /dev/null and b/sample/src/main/jniLibs/armeabi/libijksdl.so differ diff --git a/sample/src/main/jniLibs/x86/libijkffmpeg.so b/sample/src/main/jniLibs/x86/libijkffmpeg.so new file mode 100755 index 0000000..bf87655 Binary files /dev/null and b/sample/src/main/jniLibs/x86/libijkffmpeg.so differ diff --git a/sample/src/main/jniLibs/x86/libijkplayer.so b/sample/src/main/jniLibs/x86/libijkplayer.so new file mode 100755 index 0000000..57daad1 Binary files /dev/null and b/sample/src/main/jniLibs/x86/libijkplayer.so differ diff --git a/sample/src/main/jniLibs/x86/libijksdl.so b/sample/src/main/jniLibs/x86/libijksdl.so new file mode 100755 index 0000000..da19863 Binary files /dev/null and b/sample/src/main/jniLibs/x86/libijksdl.so differ diff --git a/sample/src/main/project.properties b/sample/src/main/project.properties new file mode 100755 index 0000000..672cae7 --- /dev/null +++ b/sample/src/main/project.properties @@ -0,0 +1,20 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-22 +android.library.reference.1=../../../player-arm64/src/main +android.library.reference.2=../../../player-armv5/src/main +android.library.reference.3=../../../player-armv7a/src/main +android.library.reference.4=../../../player-x86/src/main +android.library.reference.5=../../../player-java/src/main +android.library.reference.6=../../../../contrib/appcompat diff --git a/sample/src/main/res/drawable-hdpi/mediacontroller_bg.png b/sample/src/main/res/drawable-hdpi/mediacontroller_bg.png new file mode 100755 index 0000000..63b9aa5 Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/mediacontroller_bg.png differ diff --git a/sample/src/main/res/drawable-hdpi/mediacontroller_pause01.png b/sample/src/main/res/drawable-hdpi/mediacontroller_pause01.png new file mode 100755 index 0000000..698277a Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/mediacontroller_pause01.png differ diff --git a/sample/src/main/res/drawable-hdpi/mediacontroller_pause02.png b/sample/src/main/res/drawable-hdpi/mediacontroller_pause02.png new file mode 100755 index 0000000..d954a0e Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/mediacontroller_pause02.png differ diff --git a/sample/src/main/res/drawable-hdpi/mediacontroller_play01.png b/sample/src/main/res/drawable-hdpi/mediacontroller_play01.png new file mode 100755 index 0000000..81bcb26 Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/mediacontroller_play01.png differ diff --git a/sample/src/main/res/drawable-hdpi/mediacontroller_play02.png b/sample/src/main/res/drawable-hdpi/mediacontroller_play02.png new file mode 100755 index 0000000..67100f8 Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/mediacontroller_play02.png differ diff --git a/sample/src/main/res/drawable-hdpi/mediacontroller_seekbar01.png b/sample/src/main/res/drawable-hdpi/mediacontroller_seekbar01.png new file mode 100755 index 0000000..8bcdcab Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/mediacontroller_seekbar01.png differ diff --git a/sample/src/main/res/drawable-hdpi/mediacontroller_seekbar02.png b/sample/src/main/res/drawable-hdpi/mediacontroller_seekbar02.png new file mode 100755 index 0000000..b7fd9dd Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/mediacontroller_seekbar02.png differ diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_control_disabled_holo.png b/sample/src/main/res/drawable-xhdpi/scrubber_control_disabled_holo.png new file mode 100755 index 0000000..14f61b5 Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/scrubber_control_disabled_holo.png differ diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_control_focused_holo.png b/sample/src/main/res/drawable-xhdpi/scrubber_control_focused_holo.png new file mode 100755 index 0000000..5d1da3c Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/scrubber_control_focused_holo.png differ diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_control_normal_holo.png b/sample/src/main/res/drawable-xhdpi/scrubber_control_normal_holo.png new file mode 100755 index 0000000..45608c1 Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/scrubber_control_normal_holo.png differ diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_control_pressed_holo.png b/sample/src/main/res/drawable-xhdpi/scrubber_control_pressed_holo.png new file mode 100755 index 0000000..50f30c7 Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/scrubber_control_pressed_holo.png differ diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_primary_holo.9.png b/sample/src/main/res/drawable-xhdpi/scrubber_primary_holo.9.png new file mode 100755 index 0000000..6c98aa6 Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/scrubber_primary_holo.9.png differ diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_secondary_holo.9.png b/sample/src/main/res/drawable-xhdpi/scrubber_secondary_holo.9.png new file mode 100755 index 0000000..8b37b6b Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/scrubber_secondary_holo.9.png differ diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_track_holo_dark.9.png b/sample/src/main/res/drawable-xhdpi/scrubber_track_holo_dark.9.png new file mode 100755 index 0000000..a217a90 Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/scrubber_track_holo_dark.9.png differ diff --git a/sample/src/main/res/drawable/mediacontroller_pause_button.xml b/sample/src/main/res/drawable/mediacontroller_pause_button.xml new file mode 100755 index 0000000..ec96825 --- /dev/null +++ b/sample/src/main/res/drawable/mediacontroller_pause_button.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/drawable/mediacontroller_play_button.xml b/sample/src/main/res/drawable/mediacontroller_play_button.xml new file mode 100755 index 0000000..eb91676 --- /dev/null +++ b/sample/src/main/res/drawable/mediacontroller_play_button.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/drawable/scrubber_control_selector_holo.xml b/sample/src/main/res/drawable/scrubber_control_selector_holo.xml new file mode 100755 index 0000000..f64429d --- /dev/null +++ b/sample/src/main/res/drawable/scrubber_control_selector_holo.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/drawable/scrubber_progress_horizontal_holo_dark.xml b/sample/src/main/res/drawable/scrubber_progress_horizontal_holo_dark.xml new file mode 100755 index 0000000..5134258 --- /dev/null +++ b/sample/src/main/res/drawable/scrubber_progress_horizontal_holo_dark.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_file_list.xml b/sample/src/main/res/layout/activity_file_list.xml new file mode 100755 index 0000000..343304d --- /dev/null +++ b/sample/src/main/res/layout/activity_file_list.xml @@ -0,0 +1,20 @@ + + + + diff --git a/sample/src/main/res/layout/activity_player.xml b/sample/src/main/res/layout/activity_player.xml new file mode 100755 index 0000000..d49b9a9 --- /dev/null +++ b/sample/src/main/res/layout/activity_player.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/mediacontroller.xml b/sample/src/main/res/layout/mediacontroller.xml new file mode 100755 index 0000000..193c2ce --- /dev/null +++ b/sample/src/main/res/layout/mediacontroller.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/menu/menu_file_list.xml b/sample/src/main/res/menu/menu_file_list.xml new file mode 100755 index 0000000..5d40ea4 --- /dev/null +++ b/sample/src/main/res/menu/menu_file_list.xml @@ -0,0 +1,10 @@ + + + diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100755 index 0000000..cde69bc Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100755 index 0000000..c133a0c Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100755 index 0000000..bfa42f0 Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 0000000..324e72c Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/values-v11/styles.xml b/sample/src/main/res/values-v11/styles.xml new file mode 100755 index 0000000..3c02242 --- /dev/null +++ b/sample/src/main/res/values-v11/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/sample/src/main/res/values-v14/styles.xml b/sample/src/main/res/values-v14/styles.xml new file mode 100755 index 0000000..a91fd03 --- /dev/null +++ b/sample/src/main/res/values-v14/styles.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/sample/src/main/res/values-w820dp/dimens.xml b/sample/src/main/res/values-w820dp/dimens.xml new file mode 100755 index 0000000..63fc816 --- /dev/null +++ b/sample/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml new file mode 100755 index 0000000..327c060 --- /dev/null +++ b/sample/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #66000000 + + diff --git a/sample/src/main/res/values/dimens.xml b/sample/src/main/res/values/dimens.xml new file mode 100755 index 0000000..47c8224 --- /dev/null +++ b/sample/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/sample/src/main/res/values/ids.xml b/sample/src/main/res/values/ids.xml new file mode 100755 index 0000000..644e796 --- /dev/null +++ b/sample/src/main/res/values/ids.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml new file mode 100755 index 0000000..1a5ebb9 --- /dev/null +++ b/sample/src/main/res/values/strings.xml @@ -0,0 +1,20 @@ + + + + ijkmediademo + Dummy Button + DUMMY\nCONTENT + + buffering + Sort + + Vitamio + Initializing decoders… + Cannot play video + Sorry, this video is not valid for streaming to + this device. + Sorry, this video cannot be played. + OK + Play/Pause + + diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml new file mode 100755 index 0000000..ddf92f1 --- /dev/null +++ b/sample/src/main/res/values/styles.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100755 index 0000000..2273e71 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':sample', ':player-java' diff --git a/tools/gradle-mvn-push.gradle b/tools/gradle-mvn-push.gradle new file mode 100755 index 0000000..02f3061 --- /dev/null +++ b/tools/gradle-mvn-push.gradle @@ -0,0 +1,116 @@ +/* + * Copyright 2013 Chris Banes + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'maven' +apply plugin: 'signing' + +def isReleaseBuild() { + return VERSION_NAME.contains("SNAPSHOT") == false +} + +def getReleaseRepositoryUrl() { + return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL + : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" +} + +def getSnapshotRepositoryUrl() { + return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL + : "https://oss.sonatype.org/content/repositories/snapshots/" +} + +def getRepositoryUsername() { + return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" +} + +def getRepositoryPassword() { + return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" +} + +afterEvaluate { project -> + uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + + pom.groupId = GROUP + pom.artifactId = POM_ARTIFACT_ID + pom.version = VERSION_NAME + + repository(url: getReleaseRepositoryUrl()) { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + snapshotRepository(url: getSnapshotRepositoryUrl()) { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + + pom.project { + name POM_NAME + packaging POM_PACKAGING + description POM_DESCRIPTION + url POM_URL + + scm { + url POM_SCM_URL + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEV_CONNECTION + } + + licenses { + license { + name POM_LICENCE_NAME + url POM_LICENCE_URL + distribution POM_LICENCE_DIST + } + } + + developers { + developer { + id POM_DEVELOPER_ID + name POM_DEVELOPER_NAME + } + } + } + } + } + } + + signing { + required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } + sign configurations.archives + } + +/* + task androidJavadocs(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + } + + task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { + classifier = 'javadoc' + from androidJavadocs.destinationDir + } + + task androidSourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.sourceFiles + } + + artifacts { + archives androidSourcesJar + archives androidJavadocsJar + } +*/ +}