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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugAndroidTestSources
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugAndroidTestSources
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
+ }
+*/
+}