aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Fietz <Martin.Fietz@gmail.com>2015-09-19 00:54:02 +0200
committerMartin Fietz <Martin.Fietz@gmail.com>2015-09-19 14:13:07 +0200
commitedb5d72705ad70f021553161b0381209578eef31 (patch)
tree7d0fa5fe9b21fde0632ff143c501348c8ace0605
parent46ca00ef0f4d2206324ecb6177a684fc46785413 (diff)
downloadAudioPlayer-edb5d72705ad70f021553161b0381209578eef31.tar.gz
v1.0
-rw-r--r--.gitignore5
-rw-r--r--README.md8
-rw-r--r--build.gradle19
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 49896 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xgradlew164
-rw-r--r--gradlew.bat90
-rw-r--r--library/.gitignore1
-rw-r--r--library/build.gradle20
-rw-r--r--library/proguard-rules.pro22
-rw-r--r--library/src/main/AndroidManifest.xml4
-rw-r--r--library/src/main/aidl/com/aocate/presto/service/IDeathCallback_0_8.aidl18
-rw-r--r--library/src/main/aidl/com/aocate/presto/service/IOnBufferingUpdateListenerCallback_0_8.aidl19
-rw-r--r--library/src/main/aidl/com/aocate/presto/service/IOnCompletionListenerCallback_0_8.aidl19
-rw-r--r--library/src/main/aidl/com/aocate/presto/service/IOnErrorListenerCallback_0_8.aidl19
-rw-r--r--library/src/main/aidl/com/aocate/presto/service/IOnInfoListenerCallback_0_8.aidl19
-rw-r--r--library/src/main/aidl/com/aocate/presto/service/IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.aidl19
-rw-r--r--library/src/main/aidl/com/aocate/presto/service/IOnPreparedListenerCallback_0_8.aidl19
-rw-r--r--library/src/main/aidl/com/aocate/presto/service/IOnSeekCompleteListenerCallback_0_8.aidl19
-rw-r--r--library/src/main/aidl/com/aocate/presto/service/IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.aidl19
-rw-r--r--library/src/main/aidl/com/aocate/presto/service/IPlayMedia_0_8.aidl75
-rw-r--r--library/src/main/java/org/antennapod/audio/AbstractAudioPlayer.java117
-rw-r--r--library/src/main/java/org/antennapod/audio/AndroidAudioPlayer.java463
-rw-r--r--library/src/main/java/org/antennapod/audio/MediaPlayer.java1257
-rw-r--r--library/src/main/java/org/antennapod/audio/ServiceBackedAudioPlayer.java1184
-rw-r--r--library/src/main/java/org/antennapod/audio/SonicAudioPlayer.java646
-rw-r--r--library/src/main/java/org/vinuxproject/sonic/Sonic.java896
-rw-r--r--settings.gradle1
28 files changed, 5148 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index ccf2efe..8e50ce6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,9 +19,14 @@ build/
# Local configuration file (sdk path, etc)
local.properties
+gradle.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
+
+# Android Studio
+.idea/
+*.iml
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0247b9d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+# AntennaPod-AudioPlayer
+
+This is the repository for library code separated from the main repository for licensing compliance.
+
+## License
+
+All code in this repository is licensed under the Apache License, Version 2.0.
+You can find the license text in the LICENSE file.
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..1b7886d
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,19 @@
+// 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.3.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..dbed527
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Sep 18 23:51:33 CEST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-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 100644
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/library/.gitignore b/library/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/library/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/library/build.gradle b/library/build.gradle
new file mode 100644
index 0000000..acb5248
--- /dev/null
+++ b/library/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion '22.0.1'
+ defaultConfig {
+ minSdkVersion 3
+ targetSdkVersion 22
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ productFlavors {
+ }
+}
diff --git a/library/proguard-rules.pro b/library/proguard-rules.pro
new file mode 100644
index 0000000..31fd763
--- /dev/null
+++ b/library/proguard-rules.pro
@@ -0,0 +1,22 @@
+-dontobfuscate
+-optimizationpasses 5
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-dontpreverify
+-verbose
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+
+# disable logging
+-assumenosideeffects class android.util.Log {
+ public static boolean isLoggable(java.lang.String, int);
+ public static *** v(...);
+ public static *** i(...);
+ public static *** w(...);
+ public static *** d(...);
+ public static *** e(...);
+}
+
+#Keep the R
+-keepclassmembers class **.R$* {
+ public static <fields>;
+}
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..50c9b4a
--- /dev/null
+++ b/library/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="org.antennapod.audio">
+
+</manifest>
diff --git a/library/src/main/aidl/com/aocate/presto/service/IDeathCallback_0_8.aidl b/library/src/main/aidl/com/aocate/presto/service/IDeathCallback_0_8.aidl
new file mode 100644
index 0000000..6bdc768
--- /dev/null
+++ b/library/src/main/aidl/com/aocate/presto/service/IDeathCallback_0_8.aidl
@@ -0,0 +1,18 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 com.aocate.presto.service;
+
+oneway interface IDeathCallback_0_8 {
+} \ No newline at end of file
diff --git a/library/src/main/aidl/com/aocate/presto/service/IOnBufferingUpdateListenerCallback_0_8.aidl b/library/src/main/aidl/com/aocate/presto/service/IOnBufferingUpdateListenerCallback_0_8.aidl
new file mode 100644
index 0000000..7357e40
--- /dev/null
+++ b/library/src/main/aidl/com/aocate/presto/service/IOnBufferingUpdateListenerCallback_0_8.aidl
@@ -0,0 +1,19 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 com.aocate.presto.service;
+
+interface IOnBufferingUpdateListenerCallback_0_8 {
+ void onBufferingUpdate(int percent);
+} \ No newline at end of file
diff --git a/library/src/main/aidl/com/aocate/presto/service/IOnCompletionListenerCallback_0_8.aidl b/library/src/main/aidl/com/aocate/presto/service/IOnCompletionListenerCallback_0_8.aidl
new file mode 100644
index 0000000..d5edea7
--- /dev/null
+++ b/library/src/main/aidl/com/aocate/presto/service/IOnCompletionListenerCallback_0_8.aidl
@@ -0,0 +1,19 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 com.aocate.presto.service;
+
+interface IOnCompletionListenerCallback_0_8 {
+ void onCompletion();
+} \ No newline at end of file
diff --git a/library/src/main/aidl/com/aocate/presto/service/IOnErrorListenerCallback_0_8.aidl b/library/src/main/aidl/com/aocate/presto/service/IOnErrorListenerCallback_0_8.aidl
new file mode 100644
index 0000000..2c4f2df
--- /dev/null
+++ b/library/src/main/aidl/com/aocate/presto/service/IOnErrorListenerCallback_0_8.aidl
@@ -0,0 +1,19 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 com.aocate.presto.service;
+
+interface IOnErrorListenerCallback_0_8 {
+ boolean onError(int what, int extra);
+}
diff --git a/library/src/main/aidl/com/aocate/presto/service/IOnInfoListenerCallback_0_8.aidl b/library/src/main/aidl/com/aocate/presto/service/IOnInfoListenerCallback_0_8.aidl
new file mode 100644
index 0000000..9dbd1d2
--- /dev/null
+++ b/library/src/main/aidl/com/aocate/presto/service/IOnInfoListenerCallback_0_8.aidl
@@ -0,0 +1,19 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 com.aocate.presto.service;
+
+interface IOnInfoListenerCallback_0_8 {
+ boolean onInfo(int what, int extra);
+} \ No newline at end of file
diff --git a/library/src/main/aidl/com/aocate/presto/service/IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.aidl b/library/src/main/aidl/com/aocate/presto/service/IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.aidl
new file mode 100644
index 0000000..41223a9
--- /dev/null
+++ b/library/src/main/aidl/com/aocate/presto/service/IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.aidl
@@ -0,0 +1,19 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 com.aocate.presto.service;
+
+interface IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 {
+ void onPitchAdjustmentAvailableChanged(boolean pitchAdjustmentAvailable);
+} \ No newline at end of file
diff --git a/library/src/main/aidl/com/aocate/presto/service/IOnPreparedListenerCallback_0_8.aidl b/library/src/main/aidl/com/aocate/presto/service/IOnPreparedListenerCallback_0_8.aidl
new file mode 100644
index 0000000..7be8f12
--- /dev/null
+++ b/library/src/main/aidl/com/aocate/presto/service/IOnPreparedListenerCallback_0_8.aidl
@@ -0,0 +1,19 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 com.aocate.presto.service;
+
+interface IOnPreparedListenerCallback_0_8 {
+ void onPrepared();
+} \ No newline at end of file
diff --git a/library/src/main/aidl/com/aocate/presto/service/IOnSeekCompleteListenerCallback_0_8.aidl b/library/src/main/aidl/com/aocate/presto/service/IOnSeekCompleteListenerCallback_0_8.aidl
new file mode 100644
index 0000000..5bdda98
--- /dev/null
+++ b/library/src/main/aidl/com/aocate/presto/service/IOnSeekCompleteListenerCallback_0_8.aidl
@@ -0,0 +1,19 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 com.aocate.presto.service;
+
+interface IOnSeekCompleteListenerCallback_0_8 {
+ void onSeekComplete();
+} \ No newline at end of file
diff --git a/library/src/main/aidl/com/aocate/presto/service/IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.aidl b/library/src/main/aidl/com/aocate/presto/service/IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.aidl
new file mode 100644
index 0000000..a69c1cf
--- /dev/null
+++ b/library/src/main/aidl/com/aocate/presto/service/IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.aidl
@@ -0,0 +1,19 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 com.aocate.presto.service;
+
+interface IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 {
+ void onSpeedAdjustmentAvailableChanged(boolean speedAdjustmentAvailable);
+} \ No newline at end of file
diff --git a/library/src/main/aidl/com/aocate/presto/service/IPlayMedia_0_8.aidl b/library/src/main/aidl/com/aocate/presto/service/IPlayMedia_0_8.aidl
new file mode 100644
index 0000000..12a6047
--- /dev/null
+++ b/library/src/main/aidl/com/aocate/presto/service/IPlayMedia_0_8.aidl
@@ -0,0 +1,75 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 com.aocate.presto.service;
+
+import com.aocate.presto.service.IDeathCallback_0_8;
+import com.aocate.presto.service.IOnBufferingUpdateListenerCallback_0_8;
+import com.aocate.presto.service.IOnCompletionListenerCallback_0_8;
+import com.aocate.presto.service.IOnErrorListenerCallback_0_8;
+import com.aocate.presto.service.IOnPitchAdjustmentAvailableChangedListenerCallback_0_8;
+import com.aocate.presto.service.IOnPreparedListenerCallback_0_8;
+import com.aocate.presto.service.IOnSeekCompleteListenerCallback_0_8;
+import com.aocate.presto.service.IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8;
+import com.aocate.presto.service.IOnInfoListenerCallback_0_8;
+
+interface IPlayMedia_0_8 {
+ boolean canSetPitch(long sessionId);
+ boolean canSetSpeed(long sessionId);
+ float getCurrentPitchStepsAdjustment(long sessionId);
+ int getCurrentPosition(long sessionId);
+ float getCurrentSpeedMultiplier(long sessionId);
+ int getDuration(long sessionId);
+ float getMaxSpeedMultiplier(long sessionId);
+ float getMinSpeedMultiplier(long sessionId);
+ int getVersionCode();
+ String getVersionName();
+ boolean isLooping(long sessionId);
+ boolean isPlaying(long sessionId);
+ void pause(long sessionId);
+ void prepare(long sessionId);
+ void prepareAsync(long sessionId);
+ void registerOnBufferingUpdateCallback(long sessionId, IOnBufferingUpdateListenerCallback_0_8 cb);
+ void registerOnCompletionCallback(long sessionId, IOnCompletionListenerCallback_0_8 cb);
+ void registerOnErrorCallback(long sessionId, IOnErrorListenerCallback_0_8 cb);
+ void registerOnInfoCallback(long sessionId, IOnInfoListenerCallback_0_8 cb);
+ void registerOnPitchAdjustmentAvailableChangedCallback(long sessionId, IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 cb);
+ void registerOnPreparedCallback(long sessionId, IOnPreparedListenerCallback_0_8 cb);
+ void registerOnSeekCompleteCallback(long sessionId, IOnSeekCompleteListenerCallback_0_8 cb);
+ void registerOnSpeedAdjustmentAvailableChangedCallback(long sessionId, IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 cb);
+ void release(long sessionId);
+ void reset(long sessionId);
+ void seekTo(long sessionId, int msec);
+ void setAudioStreamType(long sessionId, int streamtype);
+ void setDataSourceString(long sessionId, String path);
+ void setDataSourceUri(long sessionId, in Uri uri);
+ void setEnableSpeedAdjustment(long sessionId, boolean enableSpeedAdjustment);
+ void setLooping(long sessionId, boolean looping);
+ void setPitchStepsAdjustment(long sessionId, float pitchSteps);
+ void setPlaybackPitch(long sessionId, float f);
+ void setPlaybackSpeed(long sessionId, float f);
+ void setSpeedAdjustmentAlgorithm(long sessionId, int algorithm);
+ void setVolume(long sessionId, float left, float right);
+ void start(long sessionId);
+ long startSession(IDeathCallback_0_8 cb);
+ void stop(long sessionId);
+ void unregisterOnBufferingUpdateCallback(long sessionId, IOnBufferingUpdateListenerCallback_0_8 cb);
+ void unregisterOnCompletionCallback(long sessionId, IOnCompletionListenerCallback_0_8 cb);
+ void unregisterOnErrorCallback(long sessionId, IOnErrorListenerCallback_0_8 cb);
+ void unregisterOnInfoCallback(long sessionId, IOnInfoListenerCallback_0_8 cb);
+ void unregisterOnPitchAdjustmentAvailableChangedCallback(long sessionId, IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 cb);
+ void unregisterOnPreparedCallback(long sessionId, IOnPreparedListenerCallback_0_8 cb);
+ void unregisterOnSeekCompleteCallback(long sessionId, IOnSeekCompleteListenerCallback_0_8 cb);
+ void unregisterOnSpeedAdjustmentAvailableChangedCallback(long sessionId, IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 cb);
+} \ No newline at end of file
diff --git a/library/src/main/java/org/antennapod/audio/AbstractAudioPlayer.java b/library/src/main/java/org/antennapod/audio/AbstractAudioPlayer.java
new file mode 100644
index 0000000..cbec776
--- /dev/null
+++ b/library/src/main/java/org/antennapod/audio/AbstractAudioPlayer.java
@@ -0,0 +1,117 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 org.antennapod.audio;
+
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.concurrent.locks.ReentrantLock;
+
+public abstract class AbstractAudioPlayer {
+
+ private static final String MPI_TAG = "AbstractMediaPlayer";
+ protected final MediaPlayer owningMediaPlayer;
+ protected final Context mContext;
+ protected int muteOnPreparedCount = 0;
+ protected int muteOnSeekCount = 0;
+
+ public AbstractAudioPlayer(MediaPlayer owningMediaPlayer, Context context) {
+ this.owningMediaPlayer = owningMediaPlayer;
+
+ this.mContext = context;
+ }
+
+ public abstract boolean canSetPitch();
+
+ public abstract boolean canSetSpeed();
+
+ public abstract float getCurrentPitchStepsAdjustment();
+
+ public abstract int getCurrentPosition();
+
+ public abstract float getCurrentSpeedMultiplier();
+
+ public abstract int getDuration();
+
+ public abstract float getMaxSpeedMultiplier();
+
+ public abstract float getMinSpeedMultiplier();
+
+ public abstract boolean isLooping();
+
+ public abstract boolean isPlaying();
+
+ public abstract void pause();
+
+ public abstract void prepare() throws IllegalStateException, IOException;
+
+ public abstract void prepareAsync();
+
+ public abstract void release();
+
+ public abstract void reset();
+
+ public abstract void seekTo(int msec) throws IllegalStateException;
+
+ public abstract void setAudioStreamType(int streamtype);
+
+ public abstract void setDataSource(Context context, Uri uri) throws IllegalArgumentException, IllegalStateException, IOException;
+
+ public abstract void setDataSource(String path) throws IllegalArgumentException, IllegalStateException, IOException;
+
+ public abstract void setEnableSpeedAdjustment(boolean enableSpeedAdjustment);
+
+ public abstract void setLooping(boolean loop);
+
+ public abstract void setPitchStepsAdjustment(float pitchSteps);
+
+ public abstract void setPlaybackPitch(float f);
+
+ public abstract void setPlaybackSpeed(float f);
+
+ public abstract void setVolume(float leftVolume, float rightVolume);
+
+ public abstract void setWakeMode(Context context, int mode);
+
+ public abstract void start();
+
+ public abstract void stop();
+
+ protected ReentrantLock lockMuteOnPreparedCount = new ReentrantLock();
+ public void muteNextOnPrepare() {
+ lockMuteOnPreparedCount.lock();
+ Log.d(MPI_TAG, "muteNextOnPrepare()");
+ try {
+ this.muteOnPreparedCount++;
+ }
+ finally {
+ lockMuteOnPreparedCount.unlock();
+ }
+ }
+
+ protected ReentrantLock lockMuteOnSeekCount = new ReentrantLock();
+ public void muteNextSeek() {
+ lockMuteOnSeekCount.lock();
+ Log.d(MPI_TAG, "muteNextOnSeek()");
+ try {
+ this.muteOnSeekCount++;
+ }
+ finally {
+ lockMuteOnSeekCount.unlock();
+ }
+ }
+}
diff --git a/library/src/main/java/org/antennapod/audio/AndroidAudioPlayer.java b/library/src/main/java/org/antennapod/audio/AndroidAudioPlayer.java
new file mode 100644
index 0000000..c2a5c9a
--- /dev/null
+++ b/library/src/main/java/org/antennapod/audio/AndroidAudioPlayer.java
@@ -0,0 +1,463 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 org.antennapod.audio;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.IOException;
+
+public class AndroidAudioPlayer extends AbstractAudioPlayer {
+
+ private final static String AMP_TAG = "AndroidMediaPlayer";
+
+ android.media.MediaPlayer mp = null;
+
+ private android.media.MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
+ public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
+ if (owningMediaPlayer != null) {
+ owningMediaPlayer.lock.lock();
+ try {
+ if ((owningMediaPlayer.onBufferingUpdateListener != null)
+ && (owningMediaPlayer.mpi == AndroidAudioPlayer.this)) {
+ owningMediaPlayer.onBufferingUpdateListener.onBufferingUpdate(owningMediaPlayer, percent);
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ }
+ };
+
+ private android.media.MediaPlayer.OnCompletionListener onCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
+ public void onCompletion(android.media.MediaPlayer mp) {
+ Log.d(AMP_TAG, "onCompletionListener being called");
+ if (owningMediaPlayer != null) {
+ owningMediaPlayer.lock.lock();
+ try {
+ if (owningMediaPlayer.onCompletionListener != null) {
+ owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer);
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+ }
+ };
+
+ private android.media.MediaPlayer.OnErrorListener onErrorListener = new android.media.MediaPlayer.OnErrorListener() {
+ public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
+ // Once we're in errored state, any received messages are going to be junked
+ if (owningMediaPlayer != null) {
+ owningMediaPlayer.lock.lock();
+ try {
+ if (owningMediaPlayer.onErrorListener != null) {
+ return owningMediaPlayer.onErrorListener.onError(owningMediaPlayer, what, extra);
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+ return false;
+ }
+ };
+
+ private android.media.MediaPlayer.OnInfoListener onInfoListener = new android.media.MediaPlayer.OnInfoListener() {
+ public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
+ if (owningMediaPlayer != null) {
+ owningMediaPlayer.lock.lock();
+ try {
+ if ((owningMediaPlayer.onInfoListener != null)
+ && (owningMediaPlayer.mpi == AndroidAudioPlayer.this)) {
+ return owningMediaPlayer.onInfoListener.onInfo(owningMediaPlayer, what, extra);
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+ return false;
+ }
+ };
+
+ // We have to assign this.onPreparedListener because the
+ // onPreparedListener in owningMediaPlayer sets the state
+ // to PREPARED. Due to prepareAsync, that's the only
+ // reasonable place to do it
+ // The others it just didn't make sense to have a setOnXListener that didn't use the parameter
+ private android.media.MediaPlayer.OnPreparedListener onPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
+ public void onPrepared(android.media.MediaPlayer mp) {
+ Log.d(AMP_TAG, "Calling onPreparedListener.onPrepared()");
+ if (AndroidAudioPlayer.this.owningMediaPlayer != null) {
+ AndroidAudioPlayer.this.lockMuteOnPreparedCount.lock();
+ try {
+ if (AndroidAudioPlayer.this.muteOnPreparedCount > 0) {
+ AndroidAudioPlayer.this.muteOnPreparedCount--;
+ }
+ else {
+ AndroidAudioPlayer.this.muteOnPreparedCount = 0;
+ if (AndroidAudioPlayer.this.owningMediaPlayer.onPreparedListener != null) {
+ Log.d(AMP_TAG, "Invoking AndroidMediaPlayer.this.owningMediaPlayer.onPreparedListener.onPrepared");
+ AndroidAudioPlayer.this.owningMediaPlayer.onPreparedListener.onPrepared(AndroidAudioPlayer.this.owningMediaPlayer);
+ }
+ }
+ }
+ finally {
+ AndroidAudioPlayer.this.lockMuteOnPreparedCount.unlock();
+ }
+ if (owningMediaPlayer.mpi != AndroidAudioPlayer.this) {
+ Log.d(AMP_TAG, "owningMediaPlayer has changed implementation");
+ }
+ }
+ }
+ };
+
+ private android.media.MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
+ public void onSeekComplete(android.media.MediaPlayer mp) {
+ if (owningMediaPlayer != null) {
+ owningMediaPlayer.lock.lock();
+ try {
+ lockMuteOnSeekCount.lock();
+ try {
+ if (AndroidAudioPlayer.this.muteOnSeekCount > 0) {
+ AndroidAudioPlayer.this.muteOnSeekCount--;
+ }
+ else {
+ AndroidAudioPlayer.this.muteOnSeekCount = 0;
+ if (AndroidAudioPlayer.this.owningMediaPlayer.onSeekCompleteListener != null) {
+ owningMediaPlayer.onSeekCompleteListener.onSeekComplete(owningMediaPlayer);
+ }
+ }
+ }
+ finally {
+ lockMuteOnSeekCount.unlock();
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+ }
+ };
+
+ public AndroidAudioPlayer(org.antennapod.audio.MediaPlayer owningMediaPlayer, Context context) {
+ super(owningMediaPlayer, context);
+
+ mp = new MediaPlayer();
+
+// final ReentrantLock lock = new ReentrantLock();
+// Handler handler = new Handler(Looper.getMainLooper()) {
+// @Override
+// public void handleMessage(Message msg) {
+// Log.d(AMP_TAG, "Instantiating new AndroidMediaPlayer from Handler");
+// lock.lock();
+// if (mp == null) {
+// mp = new MediaPlayer();
+// }
+// lock.unlock();
+// }
+// };
+//
+// long endTime = System.currentTimeMillis() + TIMEOUT_DURATION_MS;
+//
+// while (true) {
+// // Retry messages until mp isn't null or it's time to give up
+// handler.sendMessage(handler.obtainMessage());
+// if ((mp != null)
+// || (endTime < System.currentTimeMillis())) {
+// break;
+// }
+// try {
+// Thread.sleep(50);
+// } catch (InterruptedException e) {
+// // TODO Auto-generated catch block
+// e.printStackTrace();
+// }
+// }
+
+ if (mp == null) {
+ throw new IllegalStateException("Did not instantiate android.media.MediaPlayer successfully");
+ }
+
+ mp.setOnBufferingUpdateListener(this.onBufferingUpdateListener);
+ mp.setOnCompletionListener(this.onCompletionListener);
+ mp.setOnErrorListener(this.onErrorListener);
+ mp.setOnInfoListener(this.onInfoListener);
+ Log.d(AMP_TAG, " ++++++++++++++++++++++++++++++++ Setting prepared listener to this.onPreparedListener");
+ mp.setOnPreparedListener(this.onPreparedListener);
+ mp.setOnSeekCompleteListener(this.onSeekCompleteListener);
+ }
+
+ @Override
+ public boolean canSetPitch() {
+ return false;
+ }
+
+ @Override
+ public boolean canSetSpeed() {
+ return false;
+ }
+
+ @Override
+ public float getCurrentPitchStepsAdjustment() {
+ return 0;
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ owningMediaPlayer.lock.lock();
+ try {
+ return mp.getCurrentPosition();
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public float getCurrentSpeedMultiplier() {
+ return 1f;
+ }
+
+ @Override
+ public int getDuration() {
+ owningMediaPlayer.lock.lock();
+ try {
+ return mp.getDuration();
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public float getMaxSpeedMultiplier() {
+ return 1f;
+ }
+
+ @Override
+ public float getMinSpeedMultiplier() {
+ return 1f;
+ }
+
+ @Override
+ public boolean isLooping() {
+ owningMediaPlayer.lock.lock();
+ try {
+ return mp.isLooping();
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public boolean isPlaying() {
+ owningMediaPlayer.lock.lock();
+ try {
+ return mp.isPlaying();
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void pause() {
+ owningMediaPlayer.lock.lock();
+ try {
+ mp.pause();
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void prepare() throws IllegalStateException, IOException {
+ owningMediaPlayer.lock.lock();
+ Log.d(AMP_TAG, "prepare()");
+ try {
+ mp.prepare();
+ Log.d(AMP_TAG, "Finish prepare()");
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void prepareAsync() {
+ mp.prepareAsync();
+ }
+
+ @Override
+ public void release() {
+ owningMediaPlayer.lock.lock();
+ try {
+ if (mp != null) {
+ Log.d(AMP_TAG, "mp.release()");
+ mp.release();
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void reset() {
+ owningMediaPlayer.lock.lock();
+ try {
+ mp.reset();
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void seekTo(int msec) throws IllegalStateException {
+ owningMediaPlayer.lock.lock();
+ try {
+ mp.setOnSeekCompleteListener(this.onSeekCompleteListener);
+ mp.seekTo(msec);
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void setAudioStreamType(int streamtype) {
+ owningMediaPlayer.lock.lock();
+ try {
+ mp.setAudioStreamType(streamtype);
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void setDataSource(Context context, Uri uri)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ owningMediaPlayer.lock.lock();
+ try {
+ Log.d(AMP_TAG, "setDataSource(context, " + uri.toString() + ")");
+ mp.setDataSource(context, uri);
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void setDataSource(String path) throws IllegalArgumentException,
+ IllegalStateException, IOException {
+ owningMediaPlayer.lock.lock();
+ try {
+ Log.d(AMP_TAG, "setDataSource(" + path + ")");
+ mp.setDataSource(path);
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) {
+ // Can't!
+ }
+
+ @Override
+ public void setLooping(boolean loop) {
+ owningMediaPlayer.lock.lock();
+ try {
+ mp.setLooping(loop);
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void setPitchStepsAdjustment(float pitchSteps) {
+ // Can't!
+ }
+
+ @Override
+ public void setPlaybackPitch(float f) {
+ // Can't!
+ }
+
+ @Override
+ public void setPlaybackSpeed(float f) {
+ // Can't!
+ Log.d(AMP_TAG, "setPlaybackSpeed(" + f + ")");
+ }
+
+ @Override
+ public void setVolume(float leftVolume, float rightVolume) {
+ owningMediaPlayer.lock.lock();
+ try {
+ mp.setVolume(leftVolume, rightVolume);
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void setWakeMode(Context context, int mode) {
+ owningMediaPlayer.lock.lock();
+ try {
+ if (mode != 0) {
+ mp.setWakeMode(context, mode);
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void start() {
+ owningMediaPlayer.lock.lock();
+ try {
+ mp.start();
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ @Override
+ public void stop() {
+ owningMediaPlayer.lock.lock();
+ try {
+ mp.stop();
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+}
diff --git a/library/src/main/java/org/antennapod/audio/MediaPlayer.java b/library/src/main/java/org/antennapod/audio/MediaPlayer.java
new file mode 100644
index 0000000..3bdfd12
--- /dev/null
+++ b/library/src/main/java/org/antennapod/audio/MediaPlayer.java
@@ -0,0 +1,1257 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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 org.antennapod.audio;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class MediaPlayer {
+
+ public static final String TAG = "MediaPlayer";
+
+ public interface OnBufferingUpdateListener {
+ void onBufferingUpdate(MediaPlayer arg0, int percent);
+ }
+
+ public interface OnCompletionListener {
+ void onCompletion(MediaPlayer arg0);
+ }
+
+ public interface OnErrorListener {
+ boolean onError(MediaPlayer arg0, int what, int extra);
+ }
+
+ public interface OnInfoListener {
+ boolean onInfo(MediaPlayer arg0, int what, int extra);
+ }
+
+ public interface OnPitchAdjustmentAvailableChangedListener {
+ /**
+ * @param arg0 The owning media player
+ * @param pitchAdjustmentAvailable True if pitch adjustment is available, false if not
+ */
+ public abstract void onPitchAdjustmentAvailableChanged(
+ MediaPlayer arg0, boolean pitchAdjustmentAvailable);
+ }
+
+ public interface OnPreparedListener {
+ void onPrepared(MediaPlayer arg0);
+ }
+
+ public interface OnSeekCompleteListener {
+ void onSeekComplete(MediaPlayer arg0);
+ }
+
+ public interface OnSpeedAdjustmentAvailableChangedListener {
+ /**
+ * @param arg0 The owning media player
+ * @param speedAdjustmentAvailable True if speed adjustment is available, false if not
+ */
+ void onSpeedAdjustmentAvailableChanged(
+ MediaPlayer arg0, boolean speedAdjustmentAvailable);
+ }
+
+ public enum State {
+ IDLE, INITIALIZED, PREPARED, STARTED, PAUSED, STOPPED, PREPARING, PLAYBACK_COMPLETED, END, ERROR
+ }
+
+ private static Uri SPEED_ADJUSTMENT_MARKET_URI = Uri.parse("market://details?id=com.aocate.presto");
+
+ private static Intent prestoMarketIntent = null;
+
+ public static final int MEDIA_ERROR_SERVER_DIED = android.media.MediaPlayer.MEDIA_ERROR_SERVER_DIED;
+ public static final int MEDIA_ERROR_UNKNOWN = android.media.MediaPlayer.MEDIA_ERROR_UNKNOWN;
+ public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = android.media.MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK;
+
+ /**
+ * Indicates whether the specified action can be used as an intent. This
+ * method queries the package manager for installed packages that can
+ * respond to an intent with the specified action. If no suitable package is
+ * found, this method returns false.
+ *
+ * @param context The application's environment.
+ * @param action The Intent action to check for availability.
+ * @return True if an Intent with the specified action can be sent and
+ * responded to, false otherwise.
+ */
+ public static boolean isIntentAvailable(Context context, String action) {
+ final PackageManager packageManager = context.getPackageManager();
+ final Intent intent = new Intent(action);
+ List<ResolveInfo> list = packageManager.queryIntentServices(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ return list.size() > 0;
+ }
+
+ /**
+ * Returns an explicit Intent for a service that accepts the given Intent
+ * or null if no such service was found.
+ *
+ * @param context The application's environment.
+ * @param action The Intent action to check for availability.
+ * @return The explicit service Intent or null if no service was found.
+ */
+ public static Intent getPrestoServiceIntent(Context context, String action) {
+ final PackageManager packageManager = context.getPackageManager();
+ final Intent actionIntent = new Intent(action);
+ List<ResolveInfo> list = packageManager.queryIntentServices(actionIntent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (list.size() > 0) {
+ ResolveInfo first = list.get(0);
+ if (first.serviceInfo != null) {
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(first.serviceInfo.packageName,
+ first.serviceInfo.name));
+ Log.i(TAG, "Returning intent:" + intent.toString());
+ return intent;
+ } else {
+ Log.e(TAG, "Found service that accepts " + action + ", but serviceInfo was null");
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Indicates whether the Presto library is installed
+ *
+ * @param context The context to use to query the package manager.
+ * @return True if the Presto library is installed, false if not.
+ */
+ public static boolean isPrestoLibraryInstalled(Context context) {
+ return isIntentAvailable(context, ServiceBackedAudioPlayer.INTENT_NAME);
+ }
+
+ /**
+ * Return an Intent that opens the Android Market page for the speed
+ * alteration library
+ *
+ * @return The Intent for the Presto library on the Android Market
+ */
+ public static Intent getPrestoMarketIntent() {
+ if (prestoMarketIntent == null) {
+ prestoMarketIntent = new Intent(Intent.ACTION_VIEW, SPEED_ADJUSTMENT_MARKET_URI);
+ }
+ return prestoMarketIntent;
+ }
+
+ /**
+ * Open the Android Market page for the Presto library
+ *
+ * @param context The context from which to open the Android Market page
+ */
+ public static void openPrestoMarketIntent(Context context) {
+ context.startActivity(getPrestoMarketIntent());
+ }
+
+ private static final String MP_TAG = "ReplacementMediaPlayer";
+
+ private static final double PITCH_STEP_CONSTANT = 1.0594630943593;
+
+ private AndroidAudioPlayer amp = null;
+ private ServiceBackedAudioPlayer sbmp = null;
+ private SonicAudioPlayer smp = null;
+
+ // This is whether speed adjustment should be enabled (by the Service)
+ // To avoid the Service entirely, set useService to false
+ protected boolean enableSpeedAdjustment = true;
+ private int lastKnownPosition = 0;
+ // In some cases, we're going to have to replace the
+ // android.media.MediaPlayer on the fly, and we don't want to touch the
+ // wrong media player, so lock it way too much.
+ ReentrantLock lock = new ReentrantLock();
+ private int mAudioStreamType = AudioManager.STREAM_MUSIC;
+ private Context mContext;
+ private boolean mIsLooping = false;
+ private float mLeftVolume = 1f;
+ private float mPitchStepsAdjustment = 0f;
+ private float mRightVolume = 1f;
+ private float mSpeedMultiplier = 1f;
+ private int mWakeMode = 0;
+ AbstractAudioPlayer mpi = null;
+ protected boolean pitchAdjustmentAvailable = false;
+ protected boolean speedAdjustmentAvailable = false;
+
+ private Handler mServiceDisconnectedHandler = null;
+
+ // Some parts of state cannot be found by calling MediaPlayerImpl functions,
+ // so store our own state. This also helps copy state when changing
+ // implementations
+ State state = State.INITIALIZED;
+ String stringDataSource = null;
+ Uri uriDataSource = null;
+ private boolean useService = false;
+
+ // Naming Convention for Listeners
+ // Most listeners can both be set by clients and called by MediaPlayImpls
+ // There are a few that have to do things in this class as well as calling
+ // the function. In all cases, onX is what is called by MediaPlayerImpl
+ // If there is work to be done in this class, then the listener that is
+ // set by setX is X (with the first letter lowercase).
+ OnBufferingUpdateListener onBufferingUpdateListener = null;
+ OnCompletionListener onCompletionListener = null;
+ OnErrorListener onErrorListener = null;
+ OnInfoListener onInfoListener = null;
+
+ // Special case. Pitch adjustment ceases to be available when we switch
+ // to the android.media.MediaPlayer (though it is not guaranteed to be
+ // available when using the ServiceBackedMediaPlayer)
+ OnPitchAdjustmentAvailableChangedListener onPitchAdjustmentAvailableChangedListener = new OnPitchAdjustmentAvailableChangedListener() {
+ public void onPitchAdjustmentAvailableChanged(MediaPlayer arg0,
+ boolean pitchAdjustmentAvailable) {
+ lock.lock();
+ try {
+ Log.d(MP_TAG, "onPitchAdjustmentAvailableChangedListener.onPitchAdjustmentAvailableChanged being called");
+ if (MediaPlayer.this.pitchAdjustmentAvailable != pitchAdjustmentAvailable) {
+ Log.d(MP_TAG, "Pitch adjustment state has changed from "
+ + MediaPlayer.this.pitchAdjustmentAvailable
+ + " to " + pitchAdjustmentAvailable);
+ MediaPlayer.this.pitchAdjustmentAvailable = pitchAdjustmentAvailable;
+ if (MediaPlayer.this.pitchAdjustmentAvailableChangedListener != null) {
+ MediaPlayer.this.pitchAdjustmentAvailableChangedListener
+ .onPitchAdjustmentAvailableChanged(arg0, pitchAdjustmentAvailable);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ };
+ OnPitchAdjustmentAvailableChangedListener pitchAdjustmentAvailableChangedListener = null;
+
+ MediaPlayer.OnPreparedListener onPreparedListener = new MediaPlayer.OnPreparedListener() {
+ public void onPrepared(MediaPlayer arg0) {
+ Log.d(MP_TAG, "onPreparedListener 242 setting state to PREPARED");
+ MediaPlayer.this.state = State.PREPARED;
+ if (MediaPlayer.this.preparedListener != null) {
+ Log.d(MP_TAG, "Calling preparedListener");
+ MediaPlayer.this.preparedListener.onPrepared(arg0);
+ }
+ Log.d(MP_TAG, "Wrap up onPreparedListener");
+ }
+ };
+
+ OnPreparedListener preparedListener = null;
+ OnSeekCompleteListener onSeekCompleteListener = null;
+
+ // Special case. Speed adjustment ceases to be available when we switch
+ // to the android.media.MediaPlayer (though it is not guaranteed to be
+ // available when using the ServiceBackedMediaPlayer)
+ OnSpeedAdjustmentAvailableChangedListener onSpeedAdjustmentAvailableChangedListener = new OnSpeedAdjustmentAvailableChangedListener() {
+ public void onSpeedAdjustmentAvailableChanged(MediaPlayer arg0,
+ boolean speedAdjustmentAvailable) {
+ lock.lock();
+ try {
+ Log.d(MP_TAG, "onSpeedAdjustmentAvailableChangedListener.onSpeedAdjustmentAvailableChanged being called");
+ if (MediaPlayer.this.speedAdjustmentAvailable != speedAdjustmentAvailable) {
+ Log.d(MP_TAG, "Speed adjustment state has changed from "
+ + MediaPlayer.this.speedAdjustmentAvailable
+ + " to " + speedAdjustmentAvailable);
+ MediaPlayer.this.speedAdjustmentAvailable = speedAdjustmentAvailable;
+ if (MediaPlayer.this.speedAdjustmentAvailableChangedListener != null) {
+ MediaPlayer.this.speedAdjustmentAvailableChangedListener
+ .onSpeedAdjustmentAvailableChanged(arg0, speedAdjustmentAvailable);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ };
+ OnSpeedAdjustmentAvailableChangedListener speedAdjustmentAvailableChangedListener = null;
+
+ public MediaPlayer(final Context context) {
+ this(context, true);
+ }
+
+ public MediaPlayer(final Context context, boolean useService) {
+ this.mContext = context;
+ this.useService = useService;
+
+ // So here's the major problem
+ // Sometimes the service won't exist or won't be connected,
+ // so start with an android.media.MediaPlayer, and when
+ // the service is connected, use that from then on
+ this.mpi = this.amp = new AndroidAudioPlayer(this, context);
+ if(Build.VERSION.SDK_INT >= 16) {
+ this.smp = new SonicAudioPlayer(this, context);
+ }
+
+ // setupMpi will go get the Service, if it can, then bring that
+ // implementation into sync
+ Log.d(MP_TAG, "setupMpi");
+ setupMpi(context);
+ }
+
+ protected boolean useSonic() {
+ return false;
+ }
+
+ private boolean invalidServiceConnectionConfiguration() {
+ if(smp != null) {
+ boolean usingSonic = this.mpi instanceof SonicAudioPlayer;
+ if((usingSonic && !useSonic()) || (!usingSonic && useSonic())) {
+ return true;
+ }
+ }
+ if (!(this.mpi instanceof ServiceBackedAudioPlayer)) {
+ if (this.useService && isPrestoLibraryInstalled()) {
+ // In this case, the Presto library has been installed
+ // or something while playing sound
+ // We could be using the service, but we're not
+ Log.d(MP_TAG, "We could be using the service, but we're not");
+ return true;
+ }
+ // If useService is false, then we shouldn't be using the SBMP
+ // If the Presto library isn't installed, ditto
+ Log.d(MP_TAG, "this.mpi is not a ServiceBackedMediaPlayer, but we couldn't use it anyway");
+ return false;
+ } else {
+ if (BuildConfig.DEBUG && !(this.mpi instanceof ServiceBackedAudioPlayer))
+ throw new AssertionError();
+ if (this.useService && isPrestoLibraryInstalled()) {
+ // We should be using the service, and we are. Great!
+ Log.d(MP_TAG, "We could be using a ServiceBackedMediaPlayer and we are");
+ return false;
+ }
+ // We're trying to use the service when we shouldn't,
+ // that's an invalid configuration
+ Log.d(MP_TAG, "We're trying to use a ServiceBackedMediaPlayer but we shouldn't be");
+ return true;
+ }
+ }
+
+ private void setupMpi(final Context context) {
+ lock.lock();
+ try {
+ Log.d(MP_TAG, "setupMpi");
+ // Check if the client wants to use the service at all,
+ // then if we're already using the right kind of media player
+ if(useSonic()) {
+ if(mpi != null && mpi instanceof SonicAudioPlayer) {
+ Log.d(MP_TAG, "Already using SonicMediaPlayer");
+ return;
+ } else {
+ Log.d(MP_TAG, "Switching to SonicMediaPlayer");
+ switchMediaPlayerImpl(mpi, smp);
+ return;
+ }
+ } else if (this.useService && isPrestoLibraryInstalled()) {
+ if (mpi != null && mpi instanceof ServiceBackedAudioPlayer) {
+ Log.d(MP_TAG, "Already using ServiceBackedMediaPlayer");
+ return;
+ }
+ if (this.sbmp == null) {
+ Log.d(MP_TAG, "Instantiating new ServiceBackedMediaPlayer");
+ this.sbmp = new ServiceBackedAudioPlayer(this, context,
+ new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, final IBinder service) {
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // This lock probably isn't granular
+ // enough
+ MediaPlayer.this.lock.lock();
+ Log.d(MP_TAG, "onServiceConnected");
+ try {
+ switchMediaPlayerImpl(mpi, sbmp);
+ Log.d(MP_TAG, "End onServiceConnected");
+ } finally {
+ MediaPlayer.this.lock.unlock();
+ }
+ }
+ });
+ t.start();
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ MediaPlayer.this.lock.lock();
+ try {
+ // Can't get any more useful information
+ // out of sbmp
+ if (MediaPlayer.this.sbmp != null) {
+ MediaPlayer.this.sbmp.release();
+ }
+ // Unlike most other cases, sbmp gets set
+ // to null since there's nothing useful
+ // backing it now
+ MediaPlayer.this.sbmp = null;
+
+ if (mServiceDisconnectedHandler == null) {
+ mServiceDisconnectedHandler = new Handler(new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ // switchMediaPlayerImpl won't try to
+ // clone anything from null
+ lock.lock();
+ try {
+ if (MediaPlayer.this.amp == null) {
+ // This should never be in this state
+ MediaPlayer.this.amp = new AndroidAudioPlayer(
+ MediaPlayer.this,
+ MediaPlayer.this.mContext);
+ }
+ // Use sbmp instead of null in case by some miracle it's
+ // been restored in the meantime
+ switchMediaPlayerImpl(mpi, amp);
+ return true;
+ } finally {
+ lock.unlock();
+ }
+ }
+ });
+ }
+
+ // This code needs to execute on the
+ // original thread to instantiate
+ // the new object in the right place
+ mServiceDisconnectedHandler.sendMessage(
+ mServiceDisconnectedHandler.obtainMessage());
+ // Note that we do NOT want to set
+ // useService. useService is about
+ // what the user wants, not what they
+ // get
+ } finally {
+ MediaPlayer.this.lock.unlock();
+ }
+ }
+ }
+ );
+ }
+ Log.d(MP_TAG, "Switching to ServiceBackedMediaPlayer");
+ switchMediaPlayerImpl(mpi, sbmp);
+ } else {
+ if (this.mpi != null && this.mpi instanceof AndroidAudioPlayer) {
+ Log.d(MP_TAG, "Already using AndroidMediaPlayer");
+ return;
+ }
+ if (this.amp == null) {
+ Log.d(MP_TAG, "Instantiating new AndroidMediaPlayer (this should be impossible)");
+ this.amp = new AndroidAudioPlayer(this, context);
+ }
+ switchMediaPlayerImpl(mpi, this.amp);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private void switchMediaPlayerImpl(AbstractAudioPlayer from, AbstractAudioPlayer to) {
+ lock.lock();
+ try {
+ Log.d(MP_TAG, "switchMediaPlayerImpl");
+ if (from == to
+ // Same object, nothing to synchronize
+ || to == null
+ // Nothing to copy to (maybe this should throw an error?)
+ || (to instanceof ServiceBackedAudioPlayer && !((ServiceBackedAudioPlayer) to).isConnected())
+ // ServiceBackedMediaPlayer hasn't yet connected, onServiceConnected will take care of the transition
+ || (MediaPlayer.this.state == State.END)) {
+ // State.END is after a release(), no further functions should
+ // be called on this class and from is likely to have problems
+ // retrieving state that won't be used anyway
+ return;
+ }
+ // Extract all that we can from the existing implementation
+ // and copy it to the new implementation
+
+ Log.d(MP_TAG, "switchMediaPlayerImpl(), current state is " + this.state.toString());
+
+ to.reset();
+
+ // Do this first so we don't have to prepare the same
+ // data file twice
+ to.setEnableSpeedAdjustment(MediaPlayer.this.enableSpeedAdjustment);
+
+ // This is a reasonable place to set all of these,
+ // none of them require prepare() or the like first
+ to.setAudioStreamType(this.mAudioStreamType);
+ to.setLooping(this.mIsLooping);
+ to.setPitchStepsAdjustment(this.mPitchStepsAdjustment);
+ Log.d(MP_TAG, "Setting playback speed to " + this.mSpeedMultiplier);
+ to.setPlaybackSpeed(this.mSpeedMultiplier);
+ to.setVolume(MediaPlayer.this.mLeftVolume,
+ MediaPlayer.this.mRightVolume);
+ to.setWakeMode(this.mContext, this.mWakeMode);
+
+ Log.d(MP_TAG, "asserting at least one data source is null");
+ assert ((MediaPlayer.this.stringDataSource == null) || (MediaPlayer.this.uriDataSource == null));
+
+ if (uriDataSource != null) {
+ Log.d(MP_TAG, "switchMediaPlayerImpl(): uriDataSource != null");
+ try {
+ to.setDataSource(this.mContext, uriDataSource);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (stringDataSource != null) {
+ Log.d(MP_TAG, "switchMediaPlayerImpl(): stringDataSource != null");
+ try {
+ to.setDataSource(stringDataSource);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ if ((this.state == State.PREPARED)
+ || (this.state == State.PREPARING)
+ || (this.state == State.PAUSED)
+ || (this.state == State.STOPPED)
+ || (this.state == State.STARTED)
+ || (this.state == State.PLAYBACK_COMPLETED)) {
+ Log.d(MP_TAG, "switchMediaPlayerImpl(): prepare and seek");
+ // Use prepare here instead of prepareAsync so that
+ // we wait for it to be ready before we try to use it
+ try {
+ to.muteNextOnPrepare();
+ to.prepare();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ int seekPos = 0;
+ if (from != null) {
+ seekPos = from.getCurrentPosition();
+ } else if (this.lastKnownPosition < to.getDuration()) {
+ // This can happen if the Service unexpectedly
+ // disconnected. Because it would result in too much
+ // information being passed around, we don't constantly
+ // poll for the lastKnownPosition, but we'll save it
+ // when getCurrentPosition is called
+ seekPos = this.lastKnownPosition;
+ }
+ to.muteNextSeek();
+ to.seekTo(seekPos);
+ }
+ if ((from != null)
+ && from.isPlaying()) {
+ from.pause();
+ }
+ if ((this.state == State.STARTED)
+ || (this.state == State.PAUSED)
+ || (this.state == State.STOPPED)) {
+ Log.d(MP_TAG, "switchMediaPlayerImpl(): start");
+ if (to != null) {
+ to.start();
+ }
+ }
+
+ if (this.state == State.PAUSED) {
+ Log.d(MP_TAG, "switchMediaPlayerImpl(): paused");
+ if (to != null) {
+ to.pause();
+ }
+ } else if (this.state == State.STOPPED) {
+ Log.d(MP_TAG, "switchMediaPlayerImpl(): stopped");
+ if (to != null) {
+ to.stop();
+ }
+ }
+
+ this.mpi = to;
+ Log.d(TAG, "Switched to " + to.getClass().toString());
+
+ // Cheating here by relying on the side effect in
+ // on(Pitch|Speed)AdjustmentAvailableChanged
+ if ((to.canSetPitch() != this.pitchAdjustmentAvailable)
+ && (this.onPitchAdjustmentAvailableChangedListener != null)) {
+ this.onPitchAdjustmentAvailableChangedListener
+ .onPitchAdjustmentAvailableChanged(this, to
+ .canSetPitch());
+ }
+ if ((to.canSetSpeed() != this.speedAdjustmentAvailable)
+ && (this.onSpeedAdjustmentAvailableChangedListener != null)) {
+ this.onSpeedAdjustmentAvailableChangedListener
+ .onSpeedAdjustmentAvailableChanged(this, to
+ .canSetSpeed());
+ }
+ Log.d(MP_TAG, "switchMediaPlayerImpl() " + this.state.toString());
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns true if pitch can be changed at this moment
+ *
+ * @return True if pitch can be changed
+ */
+ public boolean canSetPitch() {
+ lock.lock();
+ try {
+ return this.mpi.canSetPitch();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns true if speed can be changed at this moment
+ *
+ * @return True if speed can be changed
+ */
+ public boolean canSetSpeed() {
+ lock.lock();
+ try {
+ return this.mpi.canSetSpeed();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ lock.lock();
+ try {
+ Log.d(MP_TAG, "finalize()");
+ this.release();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns the number of steps (in a musical scale) by which playback is
+ * currently shifted. When greater than zero, pitch is shifted up. When less
+ * than zero, pitch is shifted down.
+ *
+ * @return The number of steps pitch is currently shifted by
+ */
+ public float getCurrentPitchStepsAdjustment() {
+ lock.lock();
+ try {
+ return this.mpi.getCurrentPitchStepsAdjustment();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.getCurrentPosition()
+ * Accurate only to frame size of encoded data (26 ms for MP3s)
+ *
+ * @return Current position (in milliseconds)
+ */
+ public int getCurrentPosition() {
+ lock.lock();
+ try {
+ return (this.lastKnownPosition = this.mpi.getCurrentPosition());
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns the current speed multiplier. Defaults to 1.0 (normal speed)
+ *
+ * @return The current speed multiplier
+ */
+ public float getCurrentSpeedMultiplier() {
+ lock.lock();
+ try {
+ return this.mpi.getCurrentSpeedMultiplier();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.getDuration()
+ *
+ * @return Length of the track (in milliseconds)
+ */
+ public int getDuration() {
+ lock.lock();
+ try {
+ return this.mpi.getDuration();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Get the maximum value that can be passed to setPlaybackSpeed
+ *
+ * @return The maximum speed multiplier
+ */
+ public float getMaxSpeedMultiplier() {
+ lock.lock();
+ try {
+ return this.mpi.getMaxSpeedMultiplier();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Get the minimum value that can be passed to setPlaybackSpeed
+ *
+ * @return The minimum speed multiplier
+ */
+ public float getMinSpeedMultiplier() {
+ lock.lock();
+ try {
+ return this.mpi.getMinSpeedMultiplier();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Gets the version code of the backing service
+ *
+ * @return -1 if ServiceBackedMediaPlayer is not used, 0 if the service is not
+ * connected, otherwise the version code retrieved from the service
+ */
+ public int getServiceVersionCode() {
+ lock.lock();
+ try {
+ if (this.mpi instanceof ServiceBackedAudioPlayer) {
+ return ((ServiceBackedAudioPlayer) this.mpi).getServiceVersionCode();
+ } else {
+ return -1;
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Gets the version name of the backing service
+ *
+ * @return null if ServiceBackedMediaPlayer is not used, empty string if
+ * the service is not connected, otherwise the version name retrieved from
+ * the service
+ */
+ public String getServiceVersionName() {
+ lock.lock();
+ try {
+ if (this.mpi instanceof ServiceBackedAudioPlayer) {
+ return ((ServiceBackedAudioPlayer) this.mpi).getServiceVersionName();
+ } else {
+ return null;
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.isLooping()
+ *
+ * @return True if the track is looping
+ */
+ public boolean isLooping() {
+ lock.lock();
+ try {
+ return this.mpi.isLooping();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.isPlaying()
+ *
+ * @return True if the track is playing
+ */
+ public boolean isPlaying() {
+ lock.lock();
+ try {
+ return this.mpi.isPlaying();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns true if this MediaPlayer has access to the Presto
+ * library
+ *
+ * @return True if the Presto library is installed
+ */
+ public boolean isPrestoLibraryInstalled() {
+ if ((this.mpi == null) || (this.mpi.mContext == null)) {
+ return false;
+ }
+ return isPrestoLibraryInstalled(this.mpi.mContext);
+ }
+
+ /**
+ * Open the Android Market page in the same context as this MediaPlayer
+ */
+ public void openPrestoMarketIntent() {
+ if ((this.mpi != null) && (this.mpi.mContext != null)) {
+ openPrestoMarketIntent(this.mpi.mContext);
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.pause() Pauses the
+ * track
+ */
+ public void pause() {
+ lock.lock();
+ try {
+ if (invalidServiceConnectionConfiguration()) {
+ setupMpi(this.mpi.mContext);
+ }
+ this.state = State.PAUSED;
+ this.mpi.pause();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.prepare() Prepares the
+ * track. This or prepareAsync must be called before start()
+ */
+ public void prepare() throws IllegalStateException, IOException {
+ lock.lock();
+ try {
+ Log.d(MP_TAG, "prepare() using " + ((this.mpi == null) ? "null (this shouldn't happen)" : this.mpi.getClass().toString()) + " state " + this.state.toString());
+ Log.d(MP_TAG, "onPreparedListener is: " + ((this.onPreparedListener == null) ? "null" : "non-null"));
+ Log.d(MP_TAG, "preparedListener is: " + ((this.preparedListener == null) ? "null" : "non-null"));
+ if (invalidServiceConnectionConfiguration()) {
+ setupMpi(this.mpi.mContext);
+ }
+ this.mpi.prepare();
+ this.state = State.PREPARED;
+ Log.d(MP_TAG, "prepare() finished");
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.prepareAsync()
+ * Prepares the track. This or prepare must be called before start()
+ */
+ public void prepareAsync() {
+ lock.lock();
+ try {
+ Log.d(MP_TAG, "prepareAsync()");
+ if (invalidServiceConnectionConfiguration()) {
+ setupMpi(this.mpi.mContext);
+ }
+ this.state = State.PREPARING;
+ this.mpi.prepareAsync();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.release() Releases the
+ * underlying resources used by the media player.
+ */
+ public void release() {
+ lock.lock();
+ try {
+ Log.d(MP_TAG, "Releasing MediaPlayer");
+
+ this.state = State.END;
+ if (this.amp != null) {
+ this.amp.release();
+ }
+ if (this.sbmp != null) {
+ this.sbmp.release();
+ }
+
+ this.onBufferingUpdateListener = null;
+ this.onCompletionListener = null;
+ this.onErrorListener = null;
+ this.onInfoListener = null;
+ this.preparedListener = null;
+ this.onPitchAdjustmentAvailableChangedListener = null;
+ this.pitchAdjustmentAvailableChangedListener = null;
+ Log.d(MP_TAG, "Setting onSeekCompleteListener to null 871");
+ this.onSeekCompleteListener = null;
+ this.onSpeedAdjustmentAvailableChangedListener = null;
+ this.speedAdjustmentAvailableChangedListener = null;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.reset() Resets the
+ * track to idle state
+ */
+ public void reset() {
+ lock.lock();
+ try {
+ this.state = State.IDLE;
+ this.stringDataSource = null;
+ this.uriDataSource = null;
+ this.mpi.reset();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.seekTo(int msec) Seeks
+ * to msec in the track
+ */
+ public void seekTo(int msec) throws IllegalStateException {
+ lock.lock();
+ try {
+ this.mpi.seekTo(msec);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setAudioStreamType(int
+ * streamtype) Sets the audio stream type.
+ */
+ public void setAudioStreamType(int streamtype) {
+ lock.lock();
+ try {
+ this.mAudioStreamType = streamtype;
+ this.mpi.setAudioStreamType(streamtype);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setDataSource(Context
+ * context, Uri uri) Sets uri as data source in the context given
+ */
+ public void setDataSource(Context context, Uri uri)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ lock.lock();
+ try {
+ Log.d(MP_TAG, "In setDataSource(context, " + uri.toString() + "), using " + this.mpi.getClass().toString());
+ if (invalidServiceConnectionConfiguration()) {
+ setupMpi(this.mpi.mContext);
+ }
+ this.state = State.INITIALIZED;
+ this.stringDataSource = null;
+ this.uriDataSource = uri;
+ this.mpi.setDataSource(context, uri);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setDataSource(String
+ * path) Sets the data source of the track to a file given.
+ */
+ public void setDataSource(String path) throws IllegalArgumentException,
+ IllegalStateException, IOException {
+ lock.lock();
+ try {
+ Log.d(MP_TAG, "In setDataSource(context, " + path + ")");
+ if (invalidServiceConnectionConfiguration()) {
+ setupMpi(this.mpi.mContext);
+ }
+ this.state = State.INITIALIZED;
+ this.stringDataSource = path;
+ this.uriDataSource = null;
+ this.mpi.setDataSource(path);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Sets whether to use speed adjustment or not. Speed adjustment on is more
+ * computation-intensive than with it off.
+ *
+ * @param enableSpeedAdjustment Whether speed adjustment should be supported.
+ */
+ public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) {
+ lock.lock();
+ try {
+ this.enableSpeedAdjustment = enableSpeedAdjustment;
+ this.mpi.setEnableSpeedAdjustment(enableSpeedAdjustment);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setLooping(boolean
+ * loop) Sets the track to loop infinitely if loop is true, play once if
+ * loop is false
+ */
+ public void setLooping(boolean loop) {
+ lock.lock();
+ try {
+ this.mIsLooping = loop;
+ this.mpi.setLooping(loop);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Sets the number of steps (in a musical scale) by which playback is
+ * currently shifted. When greater than zero, pitch is shifted up. When less
+ * than zero, pitch is shifted down.
+ *
+ * @param pitchSteps The number of steps by which to shift playback
+ */
+ public void setPitchStepsAdjustment(float pitchSteps) {
+ lock.lock();
+ try {
+ this.mPitchStepsAdjustment = pitchSteps;
+ this.mpi.setPitchStepsAdjustment(pitchSteps);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private static float getPitchStepsAdjustment(float pitch) {
+ return (float) (Math.log(pitch) / (2 * Math.log(PITCH_STEP_CONSTANT)));
+ }
+
+ /**
+ * Sets the percentage by which pitch is currently shifted. When greater
+ * than zero, pitch is shifted up. When less than zero, pitch is shifted
+ * down
+ *
+ * @param pitch The percentage to shift pitch
+ */
+ public void setPlaybackPitch(float pitch) {
+ lock.lock();
+ try {
+ this.mPitchStepsAdjustment = getPitchStepsAdjustment(pitch);
+ this.mpi.setPlaybackPitch(pitch);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Set playback speed. 1.0 is normal speed, 2.0 is double speed, and so on.
+ * Speed should never be set to 0 or below.
+ *
+ * @param f The speed multiplier to use for further playback
+ */
+ public void setPlaybackSpeed(float f) {
+ lock.lock();
+ try {
+ this.mSpeedMultiplier = f;
+ this.mpi.setPlaybackSpeed(f);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setVolume(float
+ * leftVolume, float rightVolume) Sets the stereo volume
+ */
+ public void setVolume(float leftVolume, float rightVolume) {
+ lock.lock();
+ try {
+ this.mLeftVolume = leftVolume;
+ this.mRightVolume = rightVolume;
+ this.mpi.setVolume(leftVolume, rightVolume);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setWakeMode(Context
+ * context, int mode) Acquires a wake lock in the context given. You must
+ * request the appropriate permissions in your AndroidManifest.xml file.
+ */
+ public void setWakeMode(Context context, int mode) {
+ lock.lock();
+ try {
+ this.mWakeMode = mode;
+ this.mpi.setWakeMode(context, mode);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to
+ * android.media.MediaPlayer.setOnCompletionListener(OnCompletionListener
+ * listener) Sets a listener to be used when a track completes playing.
+ */
+ public void setOnBufferingUpdateListener(OnBufferingUpdateListener listener) {
+ lock.lock();
+ try {
+ this.onBufferingUpdateListener = listener;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to
+ * android.media.MediaPlayer.setOnCompletionListener(OnCompletionListener
+ * listener) Sets a listener to be used when a track completes playing.
+ */
+ public void setOnCompletionListener(OnCompletionListener listener) {
+ lock.lock();
+ try {
+ this.onCompletionListener = listener;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to
+ * android.media.MediaPlayer.setOnErrorListener(OnErrorListener listener)
+ * Sets a listener to be used when a track encounters an error.
+ */
+ public void setOnErrorListener(OnErrorListener listener) {
+ lock.lock();
+ try {
+ this.onErrorListener = listener;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to
+ * android.media.MediaPlayer.setOnInfoListener(OnInfoListener listener) Sets
+ * a listener to be used when a track has info.
+ */
+ public void setOnInfoListener(OnInfoListener listener) {
+ lock.lock();
+ try {
+ this.onInfoListener = listener;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Sets a listener that will fire when pitch adjustment becomes available or
+ * stops being available
+ */
+ public void setOnPitchAdjustmentAvailableChangedListener(
+ OnPitchAdjustmentAvailableChangedListener listener) {
+ lock.lock();
+ try {
+ this.pitchAdjustmentAvailableChangedListener = listener;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to
+ * android.media.MediaPlayer.setOnPreparedListener(OnPreparedListener
+ * listener) Sets a listener to be used when a track finishes preparing.
+ */
+ public void setOnPreparedListener(OnPreparedListener listener) {
+ lock.lock();
+ Log.d(MP_TAG, " ++++++++++++++++++++++++++++++++++++++++++++ setOnPreparedListener");
+ try {
+ this.preparedListener = listener;
+ // For this one, we do not explicitly set the MediaPlayer or the
+ // Service listener. This is because in addition to calling the
+ // listener provided by the client, it's necessary to change
+ // state to PREPARED. See prepareAsync for implementation details
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to
+ * android.media.MediaPlayer.setOnSeekCompleteListener
+ * (OnSeekCompleteListener listener) Sets a listener to be used when a track
+ * finishes seeking.
+ */
+ public void setOnSeekCompleteListener(OnSeekCompleteListener listener) {
+ lock.lock();
+ try {
+ this.onSeekCompleteListener = listener;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Sets a listener that will fire when speed adjustment becomes available or
+ * stops being available
+ */
+ public void setOnSpeedAdjustmentAvailableChangedListener(
+ OnSpeedAdjustmentAvailableChangedListener listener) {
+ lock.lock();
+ try {
+ this.speedAdjustmentAvailableChangedListener = listener;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.start() Starts a track
+ * playing
+ */
+ public void start() {
+ lock.lock();
+ try {
+ Log.d(MP_TAG, "start()");
+ if (invalidServiceConnectionConfiguration()) {
+ setupMpi(this.mpi.mContext);
+ }
+ this.state = State.STARTED;
+ this.mpi.start();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.stop() Stops a track
+ * playing and resets its position to the start.
+ */
+ public void stop() {
+ lock.lock();
+ try {
+ if (invalidServiceConnectionConfiguration()) {
+ setupMpi(this.mpi.mContext);
+ }
+ this.state = State.STOPPED;
+ this.mpi.stop();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+}
diff --git a/library/src/main/java/org/antennapod/audio/ServiceBackedAudioPlayer.java b/library/src/main/java/org/antennapod/audio/ServiceBackedAudioPlayer.java
new file mode 100644
index 0000000..238de3a
--- /dev/null
+++ b/library/src/main/java/org/antennapod/audio/ServiceBackedAudioPlayer.java
@@ -0,0 +1,1184 @@
+// Copyright 2011, Aocate, Inc.
+//
+// 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.
+//
+// -----------------------------------------------------------------------
+// Compared to the original version, this class been slightly modified so
+// that any acquired WakeLocks are only held while the MediaPlayer is
+// playing (see the stayAwake method for more details).
+
+
+package org.antennapod.audio;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.aocate.presto.service.IDeathCallback_0_8;
+import com.aocate.presto.service.IOnBufferingUpdateListenerCallback_0_8;
+import com.aocate.presto.service.IOnCompletionListenerCallback_0_8;
+import com.aocate.presto.service.IOnErrorListenerCallback_0_8;
+import com.aocate.presto.service.IOnInfoListenerCallback_0_8;
+import com.aocate.presto.service.IOnPitchAdjustmentAvailableChangedListenerCallback_0_8;
+import com.aocate.presto.service.IOnPreparedListenerCallback_0_8;
+import com.aocate.presto.service.IOnSeekCompleteListenerCallback_0_8;
+import com.aocate.presto.service.IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8;
+import com.aocate.presto.service.IPlayMedia_0_8;
+
+import java.io.IOException;
+
+/**
+ * Class for connecting to remote speed-altering, media playing Service
+ * Note that there is unusually high coupling between MediaPlayer and this
+ * class. This is an unfortunate compromise, since the alternative was to
+ * track state in two different places in this code (plus the internal state
+ * of the remote media player).
+ * @author aocate
+ *
+ */
+public class ServiceBackedAudioPlayer extends AbstractAudioPlayer {
+
+ static final String INTENT_NAME = "com.aocate.intent.PLAY_AUDIO_ADJUST_SPEED_0_8";
+
+ private static final String SBMP_TAG = "ServiceBackedMediaPlaye";
+
+ private ServiceConnection mPlayMediaServiceConnection = null;
+ protected IPlayMedia_0_8 pmInterface = null;
+ private Intent playMediaServiceIntent = null;
+ // In some cases, we're going to have to replace the
+ // android.media.MediaPlayer on the fly, and we don't want to touch the
+ // wrong media player.
+
+ private long sessionId = 0;
+ private boolean isErroring = false;
+ private int mAudioStreamType = AudioManager.STREAM_MUSIC;
+
+ private WakeLock mWakeLock = null;
+
+ // So here's the major problem
+ // Sometimes the service won't exist or won't be connected,
+ // so start with an android.media.MediaPlayer, and when
+ // the service is connected, use that from then on
+ public ServiceBackedAudioPlayer(MediaPlayer owningMediaPlayer, final Context context, final ServiceConnection serviceConnection) {
+ super(owningMediaPlayer, context);
+ Log.d(SBMP_TAG, "Instantiating ServiceBackedMediaPlayer 87");
+ this.playMediaServiceIntent =
+ MediaPlayer.getPrestoServiceIntent(context, INTENT_NAME);
+ this.mPlayMediaServiceConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ IPlayMedia_0_8 tmpPlayMediaInterface = IPlayMedia_0_8.Stub.asInterface((IBinder) service);
+
+ Log.d(SBMP_TAG, "Setting up pmInterface 94");
+ if (ServiceBackedAudioPlayer.this.sessionId == 0) {
+ try {
+ // The IDeathCallback isn't a conventional callback.
+ // It exists so that if the client ceases to exist,
+ // the Service becomes aware of that and can shut
+ // down whatever it needs to shut down
+ ServiceBackedAudioPlayer.this.sessionId = tmpPlayMediaInterface.startSession(new IDeathCallback_0_8.Stub() {
+ });
+ // This is really bad if this fails
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ Log.d(SBMP_TAG, "Assigning pmInterface");
+
+ ServiceBackedAudioPlayer.this.setOnBufferingUpdateCallback(tmpPlayMediaInterface);
+ ServiceBackedAudioPlayer.this.setOnCompletionCallback(tmpPlayMediaInterface);
+ ServiceBackedAudioPlayer.this.setOnErrorCallback(tmpPlayMediaInterface);
+ ServiceBackedAudioPlayer.this.setOnInfoCallback(tmpPlayMediaInterface);
+ ServiceBackedAudioPlayer.this.setOnPitchAdjustmentAvailableChangedListener(tmpPlayMediaInterface);
+ ServiceBackedAudioPlayer.this.setOnPreparedCallback(tmpPlayMediaInterface);
+ ServiceBackedAudioPlayer.this.setOnSeekCompleteCallback(tmpPlayMediaInterface);
+ ServiceBackedAudioPlayer.this.setOnSpeedAdjustmentAvailableChangedCallback(tmpPlayMediaInterface);
+
+ // In order to avoid race conditions from the sessionId or listener not being assigned
+ pmInterface = tmpPlayMediaInterface;
+
+ Log.d(SBMP_TAG, "Invoking onServiceConnected");
+ serviceConnection.onServiceConnected(name, service);
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ Log.d(SBMP_TAG, "onServiceDisconnected 114");
+
+ pmInterface = null;
+
+ sessionId = 0;
+
+ serviceConnection.onServiceDisconnected(name);
+ }
+ };
+
+ Log.d(SBMP_TAG, "Connecting PlayMediaService 124");
+ if (!ConnectPlayMediaService()) {
+ Log.e(SBMP_TAG, "bindService failed");
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ private boolean ConnectPlayMediaService() {
+ Log.d(SBMP_TAG, "ConnectPlayMediaService()");
+
+ if (MediaPlayer.isIntentAvailable(mContext, INTENT_NAME)) {
+ Log.d(SBMP_TAG, INTENT_NAME + " is available");
+ if (pmInterface == null) {
+ try {
+ Log.d(SBMP_TAG, "Binding service");
+ return mContext.bindService(playMediaServiceIntent, mPlayMediaServiceConnection, Context.BIND_AUTO_CREATE);
+ } catch (Exception e) {
+ Log.e(SBMP_TAG, "Could not bind with service", e);
+ return false;
+ }
+ } else {
+ Log.d(SBMP_TAG, "Service already bound");
+ return true;
+ }
+ }
+ else {
+ Log.d(SBMP_TAG, INTENT_NAME + " is not available");
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if pitch can be changed at this moment
+ * @return True if pitch can be changed
+ */
+ @Override
+ public boolean canSetPitch() {
+ Log.d(SBMP_TAG, "canSetPitch() 155");
+
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ // Can't set pitch if the service isn't connected
+ try {
+ return pmInterface.canSetPitch(ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if speed can be changed at this moment
+ * @return True if speed can be changed
+ */
+ @Override
+ public boolean canSetSpeed() {
+ Log.d(SBMP_TAG, "canSetSpeed() 180");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ // Can't set speed if the service isn't connected
+ try {
+ return pmInterface.canSetSpeed(ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ return false;
+ }
+
+ void error(int what, int extra) {
+ owningMediaPlayer.lock.lock();
+ Log.e(SBMP_TAG, "error(" + what + ", " + extra + ")");
+ stayAwake(false);
+ try {
+ if (!this.isErroring) {
+ this.isErroring = true;
+ owningMediaPlayer.state = MediaPlayer.State.ERROR;
+ if (owningMediaPlayer.onErrorListener != null) {
+ if (owningMediaPlayer.onErrorListener.onError(owningMediaPlayer, what, extra)) {
+ return;
+ }
+ }
+ if (owningMediaPlayer.onCompletionListener != null) {
+ owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer);
+ }
+ }
+ }
+ finally {
+ this.isErroring = false;
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ owningMediaPlayer.lock.lock();
+ try {
+ Log.d(SBMP_TAG, "finalize() 224");
+ this.release();
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+ /**
+ * Returns the number of steps (in a musical scale) by which playback is
+ * currently shifted. When greater than zero, pitch is shifted up.
+ * When less than zero, pitch is shifted down.
+ * @return The number of steps pitch is currently shifted by
+ */
+ @Override
+ public float getCurrentPitchStepsAdjustment() {
+ Log.d(SBMP_TAG, "getCurrentPitchStepsAdjustment() 240");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ // Can't set pitch if the service isn't connected
+ try {
+ return pmInterface.getCurrentPitchStepsAdjustment(
+ ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ return 0f;
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.getCurrentPosition()
+ * @return Current position (in milliseconds)
+ */
+ @Override
+ public int getCurrentPosition() {
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ return pmInterface.getCurrentPosition(
+ ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the current speed multiplier. Defaults to 1.0 (normal speed)
+ * @return The current speed multiplier
+ */
+ @Override
+ public float getCurrentSpeedMultiplier() {
+ Log.d(SBMP_TAG, "getCurrentSpeedMultiplier() 286");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ // Can't set speed if the service isn't connected
+ try {
+ return pmInterface.getCurrentSpeedMultiplier(
+ ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ return 1;
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.getDuration()
+ * @return Length of the track (in milliseconds)
+ */
+ @Override
+ public int getDuration() {
+ Log.d(SBMP_TAG, "getDuration() 311");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ return pmInterface.getDuration(ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ return 0;
+ }
+
+ /**
+ * Get the maximum value that can be passed to setPlaybackSpeed
+ * @return The maximum speed multiplier
+ */
+ @Override
+ public float getMaxSpeedMultiplier() {
+ Log.d(SBMP_TAG, "getMaxSpeedMultiplier() 332");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ // Can't set speed if the Service isn't connected
+ try {
+ return pmInterface.getMaxSpeedMultiplier(
+ ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ return 1f;
+ }
+
+ /**
+ * Get the minimum value that can be passed to setPlaybackSpeed
+ * @return The minimum speed multiplier
+ */
+ @Override
+ public float getMinSpeedMultiplier() {
+ Log.d(SBMP_TAG, "getMinSpeedMultiplier() 357");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ // Can't set speed if the Service isn't connected
+ try {
+ return pmInterface.getMinSpeedMultiplier(
+ ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ return 1f;
+ }
+
+ public int getServiceVersionCode() {
+ Log.d(SBMP_TAG, "getVersionCode");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ return pmInterface.getVersionCode();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ return 0;
+ }
+
+ public String getServiceVersionName() {
+ Log.d(SBMP_TAG, "getVersionName");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ return pmInterface.getVersionName();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ return "";
+ }
+
+ public boolean isConnected() {
+ return (pmInterface != null);
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.isLooping()
+ * @return True if the track is looping
+ */
+ @Override
+ public boolean isLooping() {
+ Log.d(SBMP_TAG, "isLooping() 382");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ return pmInterface.isLooping(ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ return false;
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.isPlaying()
+ * @return True if the track is playing
+ */
+ @Override
+ public boolean isPlaying() {
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ try {
+ return pmInterface.isPlaying(ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.pause()
+ * Pauses the track
+ */
+ @Override
+ public void pause() {
+ Log.d(SBMP_TAG, "pause() 424");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ pmInterface.pause(ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ stayAwake(false);
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.prepare()
+ * Prepares the track. This or prepareAsync must be called before start()
+ */
+ @Override
+ public void prepare() throws IllegalStateException, IOException {
+ Log.d(SBMP_TAG, "prepare() 444");
+ Log.d(SBMP_TAG, "onPreparedCallback is: " + ((this.mOnPreparedCallback == null) ? "null" : "non-null"));
+ if (pmInterface == null) {
+ Log.d(SBMP_TAG, "prepare: pmInterface is null");
+ if (!ConnectPlayMediaService()) {
+ Log.d(SBMP_TAG, "prepare: Failed to connect play media service");
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ Log.d(SBMP_TAG, "prepare: pmInterface isn't null");
+ try {
+ Log.d(SBMP_TAG, "prepare: Remote invoke pmInterface.prepare(" + ServiceBackedAudioPlayer.this.sessionId + ")");
+ pmInterface.prepare(ServiceBackedAudioPlayer.this.sessionId);
+ Log.d(SBMP_TAG, "prepare: prepared");
+ } catch (RemoteException e) {
+ Log.d(SBMP_TAG, "prepare: RemoteException");
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ Log.d(SBMP_TAG, "Done with prepare()");
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.prepareAsync()
+ * Prepares the track. This or prepare must be called before start()
+ */
+ @Override
+ public void prepareAsync() {
+ Log.d(SBMP_TAG, "prepareAsync() 469");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ pmInterface.prepareAsync(ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.release()
+ * Releases the underlying resources used by the media player.
+ */
+ @Override
+ public void release() {
+ Log.d(SBMP_TAG, "release() 492");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ Log.d(SBMP_TAG, "release() 500");
+ try {
+ pmInterface.release(ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ mContext.unbindService(this.mPlayMediaServiceConnection);
+ // Don't try to keep awake (if we were)
+ this.setWakeMode(mContext, 0);
+ pmInterface = null;
+ this.sessionId = 0;
+ }
+
+ if ((this.mWakeLock != null) && this.mWakeLock.isHeld()) {
+ Log.d(SBMP_TAG, "Releasing wakelock");
+ this.mWakeLock.release();
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.reset()
+ * Resets the track to idle state
+ */
+ @Override
+ public void reset() {
+ Log.d(SBMP_TAG, "reset() 523");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ pmInterface.reset(ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ stayAwake(false);
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.seekTo(int msec)
+ * Seeks to msec in the track
+ */
+ @Override
+ public void seekTo(int msec) throws IllegalStateException {
+ Log.d(SBMP_TAG, "seekTo(" + msec + ")");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ pmInterface.seekTo(ServiceBackedAudioPlayer.this.sessionId, msec);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setAudioStreamType(int streamtype)
+ * Sets the audio stream type.
+ */
+ @Override
+ public void setAudioStreamType(int streamtype) {
+ Log.d(SBMP_TAG, "setAudioStreamType(" + streamtype + ")");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ pmInterface.setAudioStreamType(
+ ServiceBackedAudioPlayer.this.sessionId,
+ this.mAudioStreamType);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setDataSource(Context context, Uri uri)
+ * Sets uri as data source in the context given
+ */
+ @Override
+ public void setDataSource(Context context, Uri uri) throws IllegalArgumentException, IllegalStateException, IOException {
+ Log.d(SBMP_TAG, "setDataSource(context, uri)");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ pmInterface.setDataSourceUri(
+ ServiceBackedAudioPlayer.this.sessionId,
+ uri);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setDataSource(String path)
+ * Sets the data source of the track to a file given.
+ */
+ @Override
+ public void setDataSource(String path) throws IllegalArgumentException, IllegalStateException, IOException {
+ Log.d(SBMP_TAG, "setDataSource(path)");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface == null) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ else {
+ try {
+ pmInterface.setDataSourceString(
+ ServiceBackedAudioPlayer.this.sessionId,
+ path);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ }
+
+ /**
+ * Sets whether to use speed adjustment or not. Speed adjustment on is
+ * more computation-intensive than with it off.
+ * @param enableSpeedAdjustment Whether speed adjustment should be supported.
+ */
+ @Override
+ public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) {
+ // TODO: This has no business being here, I think
+ owningMediaPlayer.lock.lock();
+ Log.d(SBMP_TAG, "setEnableSpeedAdjustment(enableSpeedAdjustment)");
+ try {
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ // Can't set speed if the Service isn't connected
+ try {
+ pmInterface.setEnableSpeedAdjustment(
+ ServiceBackedAudioPlayer.this.sessionId,
+ enableSpeedAdjustment);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setLooping(boolean loop)
+ * Sets the track to loop infinitely if loop is true, play once if loop is false
+ */
+ @Override
+ public void setLooping(boolean loop) {
+ Log.d(SBMP_TAG, "setLooping(" + loop + ")");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ pmInterface.setLooping(ServiceBackedAudioPlayer.this.sessionId, loop);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ /**
+ * Sets the number of steps (in a musical scale) by which playback is
+ * currently shifted. When greater than zero, pitch is shifted up.
+ * When less than zero, pitch is shifted down.
+ *
+ * @param pitchSteps The number of steps by which to shift playback
+ */
+ @Override
+ public void setPitchStepsAdjustment(float pitchSteps) {
+ Log.d(SBMP_TAG, "setPitchStepsAdjustment(" + pitchSteps + ")");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ // Can't set speed if the Service isn't connected
+ try {
+ pmInterface.setPitchStepsAdjustment(
+ ServiceBackedAudioPlayer.this.sessionId,
+ pitchSteps);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ }
+
+ /**
+ * Sets the percentage by which pitch is currently shifted. When
+ * greater than zero, pitch is shifted up. When less than zero, pitch
+ * is shifted down
+ * @param f The percentage to shift pitch
+ */
+ @Override
+ public void setPlaybackPitch(float f) {
+ Log.d(SBMP_TAG, "setPlaybackPitch(" + f + ")");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ // Can't set speed if the Service isn't connected
+ try {
+ pmInterface.setPlaybackPitch(
+ ServiceBackedAudioPlayer.this.sessionId,
+ f);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ }
+
+ /**
+ * Set playback speed. 1.0 is normal speed, 2.0 is double speed, and so
+ * on. Speed should never be set to 0 or below.
+ * @param f The speed multiplier to use for further playback
+ */
+ @Override
+ public void setPlaybackSpeed(float f) {
+ Log.d(SBMP_TAG, "setPlaybackSpeed(" + f + ")");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ if (pmInterface != null) {
+ // Can't set speed if the Service isn't connected
+ try {
+ pmInterface.setPlaybackSpeed(
+ ServiceBackedAudioPlayer.this.sessionId,
+ f);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setVolume(float leftVolume, float rightVolume)
+ * Sets the stereo volume
+ */
+ @Override
+ public void setVolume(float leftVolume, float rightVolume) {
+ Log.d(SBMP_TAG, "setVolume(" + leftVolume + ", " + rightVolume + ")");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ pmInterface.setVolume(
+ ServiceBackedAudioPlayer.this.sessionId,
+ leftVolume,
+ rightVolume);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.setWakeMode(Context context, int mode)
+ * Acquires a wake lock in the context given. You must request the appropriate permissions
+ * in your AndroidManifest.xml file.
+ */
+ @Override
+ // This does not just call .setWakeMode() in the Service because doing so
+ // would add a permission requirement to the Service. Do it here, and it's
+ // the client app's responsibility to request that permission
+ public void setWakeMode(Context context, int mode) {
+ Log.d(SBMP_TAG, "setWakeMode(context, " + mode + ")");
+ if ((this.mWakeLock != null)
+ && (this.mWakeLock.isHeld())) {
+ this.mWakeLock.release();
+ }
+ if (mode != 0) {
+ if (this.mWakeLock == null) {
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ // Since mode can't be changed on the fly, we have to allocate a new one
+ this.mWakeLock = pm.newWakeLock(mode, this.getClass().getName());
+ this.mWakeLock.setReferenceCounted(false);
+ }
+
+ this.mWakeLock.acquire();
+ }
+ }
+
+ /**
+ * Changes the state of the WakeLock if it has been acquired.
+ * If no WakeLock has been acquired with setWakeMode, this method does nothing.
+ * */
+ private void stayAwake(boolean awake) {
+ if (BuildConfig.DEBUG) Log.d(SBMP_TAG, "stayAwake(" + awake + ")");
+ if (mWakeLock != null) {
+ if (awake && !mWakeLock.isHeld()) {
+ mWakeLock.acquire();
+ } else if (!awake && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+ }
+
+ private IOnBufferingUpdateListenerCallback_0_8.Stub mOnBufferingUpdateCallback = null;
+ private void setOnBufferingUpdateCallback(IPlayMedia_0_8 iface) {
+ try {
+ if (this.mOnBufferingUpdateCallback == null) {
+ mOnBufferingUpdateCallback = new IOnBufferingUpdateListenerCallback_0_8.Stub() {
+ public void onBufferingUpdate(int percent)
+ throws RemoteException {
+ owningMediaPlayer.lock.lock();
+ try {
+ if ((owningMediaPlayer.onBufferingUpdateListener != null)
+ && (owningMediaPlayer.mpi == ServiceBackedAudioPlayer.this)) {
+ owningMediaPlayer.onBufferingUpdateListener.onBufferingUpdate(owningMediaPlayer, percent);
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+ };
+ }
+ iface.registerOnBufferingUpdateCallback(
+ ServiceBackedAudioPlayer.this.sessionId,
+ mOnBufferingUpdateCallback);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ private IOnCompletionListenerCallback_0_8.Stub mOnCompletionCallback = null;
+ private void setOnCompletionCallback(IPlayMedia_0_8 iface) {
+ try {
+ if (this.mOnCompletionCallback == null) {
+ this.mOnCompletionCallback = new IOnCompletionListenerCallback_0_8.Stub() {
+ public void onCompletion() throws RemoteException {
+ owningMediaPlayer.lock.lock();
+ Log.d(SBMP_TAG, "onCompletionListener being called");
+ stayAwake(false);
+ try {
+ if (owningMediaPlayer.onCompletionListener != null) {
+ owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer);
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+ };
+ }
+ iface.registerOnCompletionCallback(
+ ServiceBackedAudioPlayer.this.sessionId,
+ this.mOnCompletionCallback);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ private IOnErrorListenerCallback_0_8.Stub mOnErrorCallback = null;
+ private void setOnErrorCallback(IPlayMedia_0_8 iface) {
+ try {
+ if (this.mOnErrorCallback == null) {
+ this.mOnErrorCallback = new IOnErrorListenerCallback_0_8.Stub() {
+ public boolean onError(int what, int extra) throws RemoteException {
+ owningMediaPlayer.lock.lock();
+ stayAwake(false);
+ try {
+ if (owningMediaPlayer.onErrorListener != null) {
+ return owningMediaPlayer.onErrorListener.onError(owningMediaPlayer, what, extra);
+ }
+ return false;
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+ };
+ }
+ iface.registerOnErrorCallback(
+ ServiceBackedAudioPlayer.this.sessionId,
+ this.mOnErrorCallback);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ private IOnInfoListenerCallback_0_8.Stub mOnInfoCallback = null;
+ private void setOnInfoCallback(IPlayMedia_0_8 iface) {
+ try {
+ if (this.mOnInfoCallback == null) {
+ this.mOnInfoCallback = new IOnInfoListenerCallback_0_8.Stub() {
+ public boolean onInfo(int what, int extra) throws RemoteException {
+ owningMediaPlayer.lock.lock();
+ try {
+ if ((owningMediaPlayer.onInfoListener != null)
+ && (owningMediaPlayer.mpi == ServiceBackedAudioPlayer.this)) {
+ return owningMediaPlayer.onInfoListener.onInfo(owningMediaPlayer, what, extra);
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ return false;
+ }
+ };
+ }
+ iface.registerOnInfoCallback(
+ ServiceBackedAudioPlayer.this.sessionId,
+ this.mOnInfoCallback);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ private IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.Stub mOnPitchAdjustmentAvailableChangedCallback = null;
+ private void setOnPitchAdjustmentAvailableChangedListener(IPlayMedia_0_8 iface) {
+ try {
+ if (this.mOnPitchAdjustmentAvailableChangedCallback == null) {
+ this.mOnPitchAdjustmentAvailableChangedCallback = new IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.Stub() {
+ public void onPitchAdjustmentAvailableChanged(
+ boolean pitchAdjustmentAvailable)
+ throws RemoteException {
+ owningMediaPlayer.lock.lock();
+ try {
+ if (owningMediaPlayer.onPitchAdjustmentAvailableChangedListener != null) {
+ owningMediaPlayer.onPitchAdjustmentAvailableChangedListener.onPitchAdjustmentAvailableChanged(owningMediaPlayer, pitchAdjustmentAvailable);
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+ };
+ }
+ iface.registerOnPitchAdjustmentAvailableChangedCallback(
+ ServiceBackedAudioPlayer.this.sessionId,
+ this.mOnPitchAdjustmentAvailableChangedCallback);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ private IOnPreparedListenerCallback_0_8.Stub mOnPreparedCallback = null;
+ private void setOnPreparedCallback(IPlayMedia_0_8 iface) {
+ try {
+ if (this.mOnPreparedCallback == null) {
+ this.mOnPreparedCallback = new IOnPreparedListenerCallback_0_8.Stub() {
+ public void onPrepared() throws RemoteException {
+ owningMediaPlayer.lock.lock();
+ Log.d(SBMP_TAG, "setOnPreparedCallback.mOnPreparedCallback.onPrepared 1050");
+ try {
+ Log.d(SBMP_TAG, "owningMediaPlayer.onPreparedListener is " + ((owningMediaPlayer.onPreparedListener == null) ? "null" : "non-null"));
+ Log.d(SBMP_TAG, "owningMediaPlayer.mpi is " + ((owningMediaPlayer.mpi == ServiceBackedAudioPlayer.this) ? "this" : "not this"));
+ ServiceBackedAudioPlayer.this.lockMuteOnPreparedCount.lock();
+ try {
+ if (ServiceBackedAudioPlayer.this.muteOnPreparedCount > 0) {
+ ServiceBackedAudioPlayer.this.muteOnPreparedCount--;
+ }
+ else {
+ ServiceBackedAudioPlayer.this.muteOnPreparedCount = 0;
+ if (ServiceBackedAudioPlayer.this.owningMediaPlayer.onPreparedListener != null) {
+ owningMediaPlayer.onPreparedListener.onPrepared(owningMediaPlayer);
+ }
+ }
+ }
+ finally {
+ ServiceBackedAudioPlayer.this.lockMuteOnPreparedCount.unlock();
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+ };
+ }
+ iface.registerOnPreparedCallback(
+ ServiceBackedAudioPlayer.this.sessionId,
+ this.mOnPreparedCallback);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ private IOnSeekCompleteListenerCallback_0_8.Stub mOnSeekCompleteCallback = null;
+ private void setOnSeekCompleteCallback(IPlayMedia_0_8 iface) {
+ try {
+ if (this.mOnSeekCompleteCallback == null) {
+ this.mOnSeekCompleteCallback = new IOnSeekCompleteListenerCallback_0_8.Stub() {
+ public void onSeekComplete() throws RemoteException {
+ Log.d(SBMP_TAG, "onSeekComplete() 941");
+ owningMediaPlayer.lock.lock();
+ try {
+ if (ServiceBackedAudioPlayer.this.muteOnSeekCount > 0) {
+ Log.d(SBMP_TAG, "The next " + ServiceBackedAudioPlayer.this.muteOnSeekCount + " seek events are muted (counting this one)");
+ ServiceBackedAudioPlayer.this.muteOnSeekCount--;
+ }
+ else {
+ ServiceBackedAudioPlayer.this.muteOnSeekCount = 0;
+ Log.d(SBMP_TAG, "Attempting to invoke next seek event");
+ if (ServiceBackedAudioPlayer.this.owningMediaPlayer.onSeekCompleteListener != null) {
+ Log.d(SBMP_TAG, "Invoking onSeekComplete");
+ owningMediaPlayer.onSeekCompleteListener.onSeekComplete(owningMediaPlayer);
+ }
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+ };
+ }
+ iface.registerOnSeekCompleteCallback(
+ ServiceBackedAudioPlayer.this.sessionId,
+ this.mOnSeekCompleteCallback);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ private IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.Stub mOnSpeedAdjustmentAvailableChangedCallback = null;
+ private void setOnSpeedAdjustmentAvailableChangedCallback(IPlayMedia_0_8 iface) {
+ try {
+ Log.d(SBMP_TAG, "Setting the service of on speed adjustment available changed");
+ if (this.mOnSpeedAdjustmentAvailableChangedCallback == null) {
+ this.mOnSpeedAdjustmentAvailableChangedCallback = new IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.Stub() {
+ public void onSpeedAdjustmentAvailableChanged(
+ boolean speedAdjustmentAvailable)
+ throws RemoteException {
+ owningMediaPlayer.lock.lock();
+ try {
+ if (owningMediaPlayer.onSpeedAdjustmentAvailableChangedListener != null) {
+ owningMediaPlayer.onSpeedAdjustmentAvailableChangedListener.onSpeedAdjustmentAvailableChanged(owningMediaPlayer, speedAdjustmentAvailable);
+ }
+ }
+ finally {
+ owningMediaPlayer.lock.unlock();
+ }
+ }
+ };
+ }
+ iface.registerOnSpeedAdjustmentAvailableChangedCallback(
+ ServiceBackedAudioPlayer.this.sessionId,
+ this.mOnSpeedAdjustmentAvailableChangedCallback);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.start()
+ * Starts a track playing
+ */
+ @Override
+ public void start() {
+ Log.d(SBMP_TAG, "start()");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ pmInterface.start(ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ stayAwake(true);
+ }
+
+ /**
+ * Functions identically to android.media.MediaPlayer.stop()
+ * Stops a track playing and resets its position to the start.
+ */
+ @Override
+ public void stop() {
+ Log.d(SBMP_TAG, "stop()");
+ if (pmInterface == null) {
+ if (!ConnectPlayMediaService()) {
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ }
+ try {
+ pmInterface.stop(ServiceBackedAudioPlayer.this.sessionId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ ServiceBackedAudioPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ }
+ stayAwake(false);
+ }
+}
diff --git a/library/src/main/java/org/antennapod/audio/SonicAudioPlayer.java b/library/src/main/java/org/antennapod/audio/SonicAudioPlayer.java
new file mode 100644
index 0000000..44b2ae6
--- /dev/null
+++ b/library/src/main/java/org/antennapod/audio/SonicAudioPlayer.java
@@ -0,0 +1,646 @@
+//Copyright 2012 James Falcon
+//
+//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.
+
+/* Minor modifications by Martin Fietz <Martin.Fietz@gmail.com>
+ * The original source can be found here:
+ * https://github.com/TheRealFalcon/Prestissimo/blob/master/src/com/falconware/prestissimo/Track.java
+ */
+
+package org.antennapod.audio;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.net.Uri;
+import android.os.Build;
+import android.os.PowerManager;
+import android.util.Log;
+
+import org.vinuxproject.sonic.Sonic;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.locks.ReentrantLock;
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+public class SonicAudioPlayer extends AbstractAudioPlayer {
+
+ private static final String TAG = SonicAudioPlayer.class.getSimpleName();
+
+ protected final MediaPlayer mMediaPlayer;
+ private AudioTrack mTrack;
+ private Sonic mSonic;
+ private MediaExtractor mExtractor;
+ private MediaCodec mCodec;
+ private Thread mDecoderThread;
+ private String mPath;
+ private Uri mUri;
+ private final ReentrantLock mLock;
+ private final Object mDecoderLock;
+ private boolean mContinue;
+ private boolean mIsDecoding;
+ private long mDuration;
+ private float mCurrentSpeed;
+ private float mCurrentPitch;
+ private int mCurrentState;
+ private final Context mContext;
+ private PowerManager.WakeLock mWakeLock = null;
+
+ private final static int TRACK_NUM = 0;
+ private final static String TAG_TRACK = "PrestissimoTrack";
+
+ private final static int STATE_IDLE = 0;
+ private final static int STATE_INITIALIZED = 1;
+ private final static int STATE_PREPARING = 2;
+ private final static int STATE_PREPARED = 3;
+ private final static int STATE_STARTED = 4;
+ private final static int STATE_PAUSED = 5;
+ private final static int STATE_STOPPED = 6;
+ private final static int STATE_PLAYBACK_COMPLETED = 7;
+ private final static int STATE_END = 8;
+ private final static int STATE_ERROR = 9;
+
+ public SonicAudioPlayer(MediaPlayer owningMediaPlayer, Context context) {
+ super(owningMediaPlayer, context);
+ mMediaPlayer = owningMediaPlayer;
+ mCurrentState = STATE_IDLE;
+ mCurrentSpeed = 1.0f;
+ mCurrentPitch = 1.0f;
+ mContinue = false;
+ mIsDecoding = false;
+ mContext = context;
+ mPath = null;
+ mUri = null;
+ mLock = new ReentrantLock();
+ mDecoderLock = new Object();
+ }
+
+ @Override
+ public boolean canSetPitch() {
+ return true;
+ }
+
+ @Override
+ public boolean canSetSpeed() {
+ return true;
+ }
+
+ @Override
+ public float getCurrentPitchStepsAdjustment() {
+ return mCurrentPitch;
+ }
+
+ public int getCurrentPosition() {
+ switch (mCurrentState) {
+ case STATE_ERROR:
+ error();
+ break;
+ default:
+ return (int) (mExtractor.getSampleTime() / 1000);
+ }
+ return 0;
+ }
+
+ @Override
+ public float getCurrentSpeedMultiplier() {
+ return mCurrentSpeed;
+ }
+
+ public int getDuration() {
+ switch (mCurrentState) {
+ case STATE_INITIALIZED:
+ case STATE_IDLE:
+ case STATE_ERROR:
+ error();
+ break;
+ default:
+ return (int) (mDuration / 1000);
+ }
+ return 0;
+ }
+
+ @Override
+ public float getMaxSpeedMultiplier() {
+ return 4.0f;
+ }
+
+ @Override
+ public float getMinSpeedMultiplier() {
+ return 0.5f;
+ }
+
+ @Override
+ public boolean isLooping() {
+ return false;
+ }
+
+ public boolean isPlaying() {
+ switch (mCurrentState) {
+ case STATE_ERROR:
+ error();
+ break;
+ default:
+ return mCurrentState == STATE_STARTED;
+ }
+ return false;
+ }
+
+ public void pause() {
+ Log.d(TAG, "pause(), current state: " + mCurrentState);
+ switch (mCurrentState) {
+ case STATE_STARTED:
+ case STATE_PAUSED:
+ mTrack.pause();
+ mCurrentState = STATE_PAUSED;
+ Log.d(TAG_TRACK, "State changed to STATE_PAUSED");
+ break;
+ default:
+ error();
+ }
+ }
+
+ public void prepare() {
+ Log.d(TAG, "prepare(), current state: " + mCurrentState);
+ switch (mCurrentState) {
+ case STATE_INITIALIZED:
+ case STATE_STOPPED:
+ try {
+ initStream();
+ } catch (IOException e) {
+ Log.e(TAG_TRACK, "Failed setting data source!", e);
+ error();
+ return;
+ }
+ mCurrentState = STATE_PREPARED;
+ Log.d(TAG_TRACK, "State changed to STATE_PREPARED");
+ if(owningMediaPlayer.onPreparedListener != null) {
+ owningMediaPlayer.onPreparedListener.onPrepared(owningMediaPlayer);
+ }
+ break;
+ default:
+ error();
+ }
+ }
+
+ public void prepareAsync() {
+ switch (mCurrentState) {
+ case STATE_INITIALIZED:
+ case STATE_STOPPED:
+ mCurrentState = STATE_PREPARING;
+ Log.d(TAG_TRACK, "State changed to STATE_PREPARING");
+
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ initStream();
+ } catch (IOException e) {
+ Log.e(TAG_TRACK, "Failed setting data source!", e);
+ error();
+ return;
+ }
+ if (mCurrentState != STATE_ERROR) {
+ mCurrentState = STATE_PREPARED;
+ Log.d(TAG_TRACK, "State changed to STATE_PREPARED");
+ }
+ if(owningMediaPlayer.onPreparedListener != null) {
+ owningMediaPlayer.onPreparedListener.onPrepared(owningMediaPlayer);
+ }
+ }
+ });
+ t.setDaemon(true);
+ t.start();
+ break;
+ default:
+ error();
+ }
+ }
+
+ public void stop() {
+ switch (mCurrentState) {
+ case STATE_PREPARED:
+ case STATE_STARTED:
+ case STATE_STOPPED:
+ case STATE_PAUSED:
+ case STATE_PLAYBACK_COMPLETED:
+ mCurrentState = STATE_STOPPED;
+ Log.d(TAG_TRACK, "State changed to STATE_STOPPED");
+ mContinue = false;
+ mTrack.pause();
+ mTrack.flush();
+ break;
+ default:
+ error();
+ }
+ }
+
+ public void start() {
+ switch (mCurrentState) {
+ case STATE_PREPARED:
+ case STATE_PLAYBACK_COMPLETED:
+ mCurrentState = STATE_STARTED;
+ Log.d(TAG, "State changed to STATE_STARTED");
+ mContinue = true;
+ mTrack.play();
+ decode();
+ case STATE_STARTED:
+ break;
+ case STATE_PAUSED:
+ mCurrentState = STATE_STARTED;
+ Log.d(TAG, "State changed to STATE_STARTED");
+ synchronized (mDecoderLock) {
+ mDecoderLock.notify();
+ }
+ mTrack.play();
+ break;
+ default:
+ mCurrentState = STATE_ERROR;
+ Log.d(TAG, "State changed to STATE_ERROR in start");
+ if (mTrack != null) {
+ error();
+ } else {
+ Log.d("start",
+ "Attempting to start while in idle after construction. Not allowed by no callbacks called");
+ }
+ }
+ }
+
+ public void release() {
+ reset();
+ mCurrentState = STATE_END;
+ }
+
+ public void reset() {
+ mLock.lock();
+ mContinue = false;
+ try {
+ if (mDecoderThread != null
+ && mCurrentState != STATE_PLAYBACK_COMPLETED) {
+ while (mIsDecoding) {
+ synchronized (mDecoderLock) {
+ mDecoderLock.notify();
+ mDecoderLock.wait();
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG_TRACK,
+ "Interrupted in reset while waiting for decoder thread to stop.",
+ e);
+ }
+ if (mCodec != null) {
+ mCodec.release();
+ mCodec = null;
+ }
+ if (mExtractor != null) {
+ mExtractor.release();
+ mExtractor = null;
+ }
+ if (mTrack != null) {
+ mTrack.release();
+ mTrack = null;
+ }
+ mCurrentState = STATE_IDLE;
+ Log.d(TAG_TRACK, "State changed to STATE_IDLE");
+ mLock.unlock();
+ }
+
+ public void seekTo(final int msec) {
+ switch (mCurrentState) {
+ case STATE_PREPARED:
+ case STATE_STARTED:
+ case STATE_PAUSED:
+ case STATE_PLAYBACK_COMPLETED:
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ mLock.lock();
+ if (mTrack == null) {
+ return;
+ }
+ mTrack.flush();
+ mExtractor.seekTo(((long) msec * 1000), MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ Log.d(TAG, "seek completed");
+ if(owningMediaPlayer.onSeekCompleteListener != null) {
+ owningMediaPlayer.onSeekCompleteListener.onSeekComplete(owningMediaPlayer);
+ }
+ mLock.unlock();
+ }
+ });
+ t.setDaemon(true);
+ t.start();
+ break;
+ default:
+ error();
+ }
+ }
+
+ @Override
+ public void setAudioStreamType(int streamtype) {
+ return;
+ }
+
+ @Override
+ public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) {
+ return;
+ }
+
+ @Override
+ public void setLooping(boolean loop) {
+ return;
+ }
+
+ @Override
+ public void setPitchStepsAdjustment(float pitchSteps) {
+ mCurrentPitch += pitchSteps;
+ }
+
+ @Override
+ public void setPlaybackPitch(float f) {
+ mCurrentSpeed = f;
+ }
+
+ @Override
+ public void setPlaybackSpeed(float f) {
+ mCurrentSpeed = f;
+ }
+
+ @Override
+ public void setDataSource(String path) {
+ switch (mCurrentState) {
+ case STATE_IDLE:
+ mPath = path;
+ mCurrentState = STATE_INITIALIZED;
+ Log.d(TAG_TRACK, "Moving state to STATE_INITIALIZED");
+ break;
+ default:
+ error();
+ }
+ }
+
+ @Override
+ public void setDataSource(Context context, Uri uri) {
+ switch (mCurrentState) {
+ case STATE_IDLE:
+ mUri = uri;
+ mCurrentState = STATE_INITIALIZED;
+ Log.d(TAG_TRACK, "Moving state to STATE_INITIALIZED");
+ break;
+ default:
+ error();
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void setVolume(float leftVolume, float rightVolume) {
+ // Pass call directly to AudioTrack if available.
+ if (null != mTrack) {
+ mTrack.setStereoVolume(leftVolume, rightVolume);
+ }
+ }
+
+ @Override
+ public void setWakeMode(Context context, int mode) {
+ boolean wasHeld = false;
+ if (mWakeLock != null) {
+ if (mWakeLock.isHeld()) {
+ wasHeld = true;
+ mWakeLock.release();
+ }
+ mWakeLock = null;
+ }
+
+ if(mode > 0) {
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(mode, this.getClass().getName());
+ mWakeLock.setReferenceCounted(false);
+ if (wasHeld) {
+ mWakeLock.acquire();
+ }
+ }
+ }
+
+ public void error() {
+ error(0);
+ }
+
+ public void error(int extra) {
+ Log.e(TAG_TRACK, "Moved to error state!");
+ mCurrentState = STATE_ERROR;
+ if(owningMediaPlayer.onErrorListener != null) {
+ boolean handled = owningMediaPlayer.onErrorListener.onError(owningMediaPlayer, 0, extra);
+ if (!handled && owningMediaPlayer.onCompletionListener != null) {
+ owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer);
+ }
+ }
+ }
+
+ private int findFormatFromChannels(int numChannels) {
+ switch (numChannels) {
+ case 1:
+ return AudioFormat.CHANNEL_OUT_MONO;
+ case 2:
+ return AudioFormat.CHANNEL_OUT_STEREO;
+ default:
+ return -1; // Error
+ }
+ }
+
+ public void initStream() throws IOException {
+ mLock.lock();
+ mExtractor = new MediaExtractor();
+ if (mPath != null) {
+ mExtractor.setDataSource(mPath);
+ } else if (mUri != null) {
+ mExtractor.setDataSource(mContext, mUri, null);
+ } else {
+ throw new IOException();
+ }
+
+ final MediaFormat oFormat = mExtractor.getTrackFormat(TRACK_NUM);
+ int sampleRate = oFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+ int channelCount = oFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+ final String mime = oFormat.getString(MediaFormat.KEY_MIME);
+ mDuration = oFormat.getLong(MediaFormat.KEY_DURATION);
+
+ Log.v(TAG_TRACK, "Sample rate: " + sampleRate);
+ Log.v(TAG_TRACK, "Mime type: " + mime);
+
+ initDevice(sampleRate, channelCount);
+ mExtractor.selectTrack(TRACK_NUM);
+ mCodec = MediaCodec.createDecoderByType(mime);
+ mCodec.configure(oFormat, null, null, 0);
+ mLock.unlock();
+ }
+
+ private void initDevice(int sampleRate, int numChannels) {
+ mLock.lock();
+ final int format = findFormatFromChannels(numChannels);
+ final int minSize = AudioTrack.getMinBufferSize(sampleRate, format,
+ AudioFormat.ENCODING_PCM_16BIT);
+ mTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, format,
+ AudioFormat.ENCODING_PCM_16BIT, minSize * 4,
+ AudioTrack.MODE_STREAM);
+ mSonic = new Sonic(sampleRate, numChannels);
+ mLock.unlock();
+ }
+
+ @SuppressWarnings("deprecation")
+ public void decode() {
+ mDecoderThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+
+ mIsDecoding = true;
+ mCodec.start();
+
+ ByteBuffer[] inputBuffers = mCodec.getInputBuffers();
+ ByteBuffer[] outputBuffers = mCodec.getOutputBuffers();
+
+ boolean sawInputEOS = false;
+ boolean sawOutputEOS = false;
+
+ while (!sawInputEOS && !sawOutputEOS && mContinue) {
+ if (mCurrentState == STATE_PAUSED) {
+ System.out.println("Decoder changed to PAUSED");
+ try {
+ synchronized (mDecoderLock) {
+ mDecoderLock.wait();
+ System.out.println("Done with wait");
+ }
+ } catch (InterruptedException e) {
+ // Purposely not doing anything here
+ }
+ continue;
+ }
+
+ if (null != mSonic) {
+ mSonic.setSpeed(mCurrentSpeed);
+ mSonic.setPitch(mCurrentPitch);
+ }
+
+ int inputBufIndex = mCodec.dequeueInputBuffer(200);
+ if (inputBufIndex >= 0) {
+ ByteBuffer dstBuf = inputBuffers[inputBufIndex];
+ int sampleSize = mExtractor.readSampleData(dstBuf, 0);
+ long presentationTimeUs = 0;
+ if (sampleSize < 0) {
+ sawInputEOS = true;
+ sampleSize = 0;
+ } else {
+ presentationTimeUs = mExtractor.getSampleTime();
+ }
+ mCodec.queueInputBuffer(
+ inputBufIndex,
+ 0,
+ sampleSize,
+ presentationTimeUs,
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+ if (!sawInputEOS) {
+ mExtractor.advance();
+ }
+ }
+
+ final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ byte[] modifiedSamples = new byte[info.size];
+
+ int res;
+ do {
+ res = mCodec.dequeueOutputBuffer(info, 200);
+ if (res >= 0) {
+ int outputBufIndex = res;
+ final byte[] chunk = new byte[info.size];
+ outputBuffers[res].get(chunk);
+ outputBuffers[res].clear();
+
+ if (chunk.length > 0) {
+ mSonic.writeBytesToStream(chunk, chunk.length);
+ } else {
+ mSonic.flushStream();
+ }
+ int available = mSonic.samplesAvailable();
+ if (available > 0) {
+ if (modifiedSamples.length < available) {
+ modifiedSamples = new byte[available];
+ }
+ mSonic.readBytesFromStream(modifiedSamples, available);
+ mTrack.write(modifiedSamples, 0, available);
+ }
+
+ mCodec.releaseOutputBuffer(outputBufIndex, false);
+
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ sawOutputEOS = true;
+ }
+ } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ outputBuffers = mCodec.getOutputBuffers();
+ Log.d("PCM", "Output buffers changed");
+ } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ mTrack.stop();
+ mLock.lock();
+ mTrack.release();
+ final MediaFormat oformat = mCodec.getOutputFormat();
+ Log.d("PCM", "Output format has changed to" + oformat);
+ initDevice(
+ oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE),
+ oformat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+ outputBuffers = mCodec.getOutputBuffers();
+ mTrack.play();
+ mLock.unlock();
+ }
+ } while (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED
+ || res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
+ }
+ Log.d(TAG_TRACK, "Decoding loop exited. Stopping codec and track");
+ Log.d(TAG_TRACK, "Duration: " + (int) (mDuration / 1000));
+ Log.d(TAG_TRACK, "Current position: " + (int) (mExtractor.getSampleTime() / 1000));
+ mCodec.stop();
+ mTrack.stop();
+ Log.d(TAG_TRACK, "Stopped codec and track");
+ Log.d(TAG_TRACK, "Current position: " + (int) (mExtractor.getSampleTime() / 1000));
+ mIsDecoding = false;
+ if (mContinue && (sawInputEOS || sawOutputEOS)) {
+ mCurrentState = STATE_PLAYBACK_COMPLETED;
+ if (owningMediaPlayer.onCompletionListener != null) {
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer);
+
+ }
+ });
+ t.setDaemon(true);
+ t.start();
+ }
+ } else {
+ Log.d(TAG_TRACK, "Loop ended before saw input eos or output eos");
+ Log.d(TAG_TRACK, "sawInputEOS: " + sawInputEOS);
+ Log.d(TAG_TRACK, "sawOutputEOS: " + sawOutputEOS);
+ }
+ synchronized (mDecoderLock) {
+ mDecoderLock.notifyAll();
+ }
+ }
+ });
+ mDecoderThread.setDaemon(true);
+ mDecoderThread.start();
+ }
+
+}
diff --git a/library/src/main/java/org/vinuxproject/sonic/Sonic.java b/library/src/main/java/org/vinuxproject/sonic/Sonic.java
new file mode 100644
index 0000000..975085e
--- /dev/null
+++ b/library/src/main/java/org/vinuxproject/sonic/Sonic.java
@@ -0,0 +1,896 @@
+/* Sonic library
+ Copyright 2010, 2011
+ Bill Cox
+ This file is part of the Sonic Library.
+
+ This file is licensed under the Apache 2.0 license.
+*/
+
+/* Minor modifications by Martin Fietz <Martin.Fietz@gmail.com>
+ * The original source can be found here:
+ * https://github.com/waywardgeek/sonic/blob/master/Sonic.java
+ */
+
+
+
+package org.vinuxproject.sonic;
+
+public class Sonic {
+
+ private static final int SONIC_MIN_PITCH = 65;
+ private static final int SONIC_MAX_PITCH = 400;
+ /* This is used to down-sample some inputs to improve speed */
+ private static final int SONIC_AMDF_FREQ = 4000;
+
+ private short inputBuffer[];
+ private short outputBuffer[];
+ private short pitchBuffer[];
+ private short downSampleBuffer[];
+ private float speed;
+ private float volume;
+ private float pitch;
+ private float rate;
+ private int oldRatePosition;
+ private int newRatePosition;
+ private boolean useChordPitch;
+ private int quality;
+ private int numChannels;
+ private int inputBufferSize;
+ private int pitchBufferSize;
+ private int outputBufferSize;
+ private int numInputSamples;
+ private int numOutputSamples;
+ private int numPitchSamples;
+ private int minPeriod;
+ private int maxPeriod;
+ private int maxRequired;
+ private int remainingInputToCopy;
+ private int sampleRate;
+ private int prevPeriod;
+ private int prevMinDiff;
+
+ // Resize the array.
+ private short[] resize(short[] oldArray,
+ int newLength) {
+ newLength *= numChannels;
+ short[] newArray = new short[newLength];
+ int length = oldArray.length <= newLength ? oldArray.length : newLength;
+
+ System.arraycopy(oldArray, 0, newArray, 0, length);
+
+ return newArray;
+ }
+
+ // Move samples from one array to another. May move samples down within an array, but not up.
+ private void move(short dest[], int destPos,
+ short source[],
+ int sourcePos,
+ int numSamples) {
+ for (int xSample = 0; xSample < numSamples * numChannels; xSample++) {
+ dest[destPos * numChannels + xSample] = source[sourcePos * numChannels + xSample];
+ }
+ }
+
+ // Scale the samples by the factor.
+ private void scaleSamples(short samples[],
+ int position,
+ int numSamples,
+ float volume) {
+ int fixedPointVolume = (int) (volume * 4096.0f);
+ int start = position * numChannels;
+ int stop = start + numSamples * numChannels;
+
+ for (int xSample = start; xSample < stop; xSample++) {
+ int value = (samples[xSample] * fixedPointVolume) >> 12;
+ if (value > 32767) {
+ value = 32767;
+ } else if (value < -32767) {
+ value = -32767;
+ }
+ samples[xSample] = (short) value;
+ }
+ }
+
+ // Get the speed of the stream.
+ public float getSpeed() {
+ return speed;
+ }
+
+ // Set the speed of the stream.
+ public void setSpeed(float speed) {
+ this.speed = speed;
+ }
+
+ // Get the pitch of the stream.
+ public float getPitch() {
+ return pitch;
+ }
+
+ // Set the pitch of the stream.
+ public void setPitch(float pitch) {
+ this.pitch = pitch;
+ }
+
+ // Get the rate of the stream.
+ public float getRate() {
+ return rate;
+ }
+
+ // Set the playback rate of the stream. This scales pitch and speed at the same time.
+ public void setRate(float rate) {
+ this.rate = rate;
+ this.oldRatePosition = 0;
+ this.newRatePosition = 0;
+ }
+
+ // Get the vocal chord pitch setting.
+ public boolean getChordPitch() {
+ return useChordPitch;
+ }
+
+ // Set the vocal chord mode for pitch computation. Default is off.
+ public void setChordPitch(
+ boolean useChordPitch) {
+ this.useChordPitch = useChordPitch;
+ }
+
+ // Get the quality setting.
+ public int getQuality() {
+ return quality;
+ }
+
+ // Set the "quality". Default 0 is virtually as good as 1, but very much faster.
+ public void setQuality(int quality) {
+ this.quality = quality;
+ }
+
+ // Get the scaling factor of the stream.
+ public float getVolume() {
+ return volume;
+ }
+
+ // Set the scaling factor of the stream.
+ public void setVolume(float volume) {
+ this.volume = volume;
+ }
+
+ // Allocate stream buffers.
+ private void allocateStreamBuffers(int sampleRate,
+ int numChannels) {
+ minPeriod = sampleRate / SONIC_MAX_PITCH;
+ maxPeriod = sampleRate / SONIC_MIN_PITCH;
+ maxRequired = 2 * maxPeriod;
+ inputBufferSize = maxRequired;
+ inputBuffer = new short[maxRequired * numChannels];
+ outputBufferSize = maxRequired;
+ outputBuffer = new short[maxRequired * numChannels];
+ pitchBufferSize = maxRequired;
+ pitchBuffer = new short[maxRequired * numChannels];
+ downSampleBuffer = new short[maxRequired];
+ this.sampleRate = sampleRate;
+ this.numChannels = numChannels;
+ oldRatePosition = 0;
+ newRatePosition = 0;
+ prevPeriod = 0;
+ }
+
+ // Create a sonic stream.
+ public Sonic(int sampleRate,
+ int numChannels) {
+ allocateStreamBuffers(sampleRate, numChannels);
+ speed = 1.0f;
+ pitch = 1.0f;
+ volume = 1.0f;
+ rate = 1.0f;
+ oldRatePosition = 0;
+ newRatePosition = 0;
+ useChordPitch = false;
+ quality = 0;
+ }
+
+ // Get the sample rate of the stream.
+ public int getSampleRate() {
+ return sampleRate;
+ }
+
+ // Set the sample rate of the stream. This will cause samples buffered in the stream to be lost.
+ public void setSampleRate(int sampleRate) {
+ allocateStreamBuffers(sampleRate, numChannels);
+ }
+
+ // Get the number of channels.
+ public int getNumChannels() {
+ return numChannels;
+ }
+
+ // Set the num channels of the stream. This will cause samples buffered in the stream to be lost.
+ public void setNumChannels(int numChannels) {
+ allocateStreamBuffers(sampleRate, numChannels);
+ }
+
+ // Enlarge the output buffer if needed.
+ private void enlargeOutputBufferIfNeeded(int numSamples) {
+ if (numOutputSamples + numSamples > outputBufferSize) {
+ outputBufferSize += (outputBufferSize >> 1) + numSamples;
+ outputBuffer = resize(outputBuffer, outputBufferSize);
+ }
+ }
+
+ // Enlarge the input buffer if needed.
+ private void enlargeInputBufferIfNeeded(int numSamples) {
+ if (numInputSamples + numSamples > inputBufferSize) {
+ inputBufferSize += (inputBufferSize >> 1) + numSamples;
+ inputBuffer = resize(inputBuffer, inputBufferSize);
+ }
+ }
+
+ // Add the input samples to the input buffer.
+ private void addFloatSamplesToInputBuffer(float samples[],
+ int numSamples) {
+ if (numSamples == 0) {
+ return;
+ }
+ enlargeInputBufferIfNeeded(numSamples);
+ int xBuffer = numInputSamples * numChannels;
+ for (int xSample = 0; xSample < numSamples * numChannels; xSample++) {
+ inputBuffer[xBuffer++] = (short) (samples[xSample] * 32767.0f);
+ }
+ numInputSamples += numSamples;
+ }
+
+ // Add the input samples to the input buffer.
+ private void addShortSamplesToInputBuffer(short samples[],
+ int numSamples) {
+ if (numSamples == 0) {
+ return;
+ }
+ enlargeInputBufferIfNeeded(numSamples);
+ move(inputBuffer, numInputSamples, samples, 0, numSamples);
+ numInputSamples += numSamples;
+ }
+
+ // Add the input samples to the input buffer.
+ private void addUnsignedByteSamplesToInputBuffer(byte samples[],
+ int numSamples) {
+ short sample;
+
+ enlargeInputBufferIfNeeded(numSamples);
+ int xBuffer = numInputSamples * numChannels;
+ for (int xSample = 0; xSample < numSamples * numChannels; xSample++) {
+ sample = (short) ((samples[xSample] & 0xff) - 128); // Convert from unsigned to signed
+ inputBuffer[xBuffer++] = (short) (sample << 8);
+ }
+ numInputSamples += numSamples;
+ }
+
+ // Add the input samples to the input buffer. They must be 16-bit little-endian encoded in a byte array.
+ private void addBytesToInputBuffer(byte inBuffer[],
+ int numBytes) {
+ int numSamples = numBytes / (2 * numChannels);
+ short sample;
+
+ enlargeInputBufferIfNeeded(numSamples);
+ int xBuffer = numInputSamples * numChannels;
+ for (int xByte = 0; xByte + 1 < numBytes; xByte += 2) {
+ sample = (short) ((inBuffer[xByte] & 0xff) | (inBuffer[xByte + 1] << 8));
+ inputBuffer[xBuffer++] = sample;
+ }
+ numInputSamples += numSamples;
+ }
+
+ // Remove input samples that we have already processed.
+ private void removeInputSamples(int position) {
+ int remainingSamples = numInputSamples - position;
+
+ move(inputBuffer, 0, inputBuffer, position, remainingSamples);
+ numInputSamples = remainingSamples;
+ }
+
+ // Just copy from the array to the output buffer
+ private void copyToOutput(short samples[],
+ int position,
+ int numSamples) {
+ enlargeOutputBufferIfNeeded(numSamples);
+ move(outputBuffer, numOutputSamples, samples, position, numSamples);
+ numOutputSamples += numSamples;
+ }
+
+ // Just copy from the input buffer to the output buffer. Return num samples copied.
+ private int copyInputToOutput(int position) {
+ int numSamples = remainingInputToCopy;
+
+ if (numSamples > maxRequired) {
+ numSamples = maxRequired;
+ }
+ copyToOutput(inputBuffer, position, numSamples);
+ remainingInputToCopy -= numSamples;
+ return numSamples;
+ }
+
+ // Read data out of the stream. Sometimes no data will be available, and zero
+ // is returned, which is not an error condition.
+ public int readFloatFromStream(float samples[],
+ int maxSamples) {
+ int numSamples = numOutputSamples;
+ int remainingSamples = 0;
+
+ if (numSamples == 0) {
+ return 0;
+ }
+ if (numSamples > maxSamples) {
+ remainingSamples = numSamples - maxSamples;
+ numSamples = maxSamples;
+ }
+ for (int xSample = 0; xSample < numSamples * numChannels; xSample++) {
+ samples[xSample++] = (outputBuffer[xSample]) / 32767.0f;
+ }
+ move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
+ numOutputSamples = remainingSamples;
+ return numSamples;
+ }
+
+ // Read short data out of the stream. Sometimes no data will be available, and zero
+ // is returned, which is not an error condition.
+ public int readShortFromStream(short samples[],
+ int maxSamples) {
+ int numSamples = numOutputSamples;
+ int remainingSamples = 0;
+
+ if (numSamples == 0) {
+ return 0;
+ }
+ if (numSamples > maxSamples) {
+ remainingSamples = numSamples - maxSamples;
+ numSamples = maxSamples;
+ }
+ move(samples, 0, outputBuffer, 0, numSamples);
+ move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
+ numOutputSamples = remainingSamples;
+ return numSamples;
+ }
+
+ // Read unsigned byte data out of the stream. Sometimes no data will be available, and zero
+ // is returned, which is not an error condition.
+ public int readUnsignedByteFromStream(byte samples[],
+ int maxSamples) {
+ int numSamples = numOutputSamples;
+ int remainingSamples = 0;
+
+ if (numSamples == 0) {
+ return 0;
+ }
+ if (numSamples > maxSamples) {
+ remainingSamples = numSamples - maxSamples;
+ numSamples = maxSamples;
+ }
+ for (int xSample = 0; xSample < numSamples * numChannels; xSample++) {
+ samples[xSample] = (byte) ((outputBuffer[xSample] >> 8) + 128);
+ }
+ move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
+ numOutputSamples = remainingSamples;
+ return numSamples;
+ }
+
+ // Read unsigned byte data out of the stream. Sometimes no data will be available, and zero
+ // is returned, which is not an error condition.
+ public int readBytesFromStream(byte outBuffer[],
+ int maxBytes) {
+ int maxSamples = maxBytes / (2 * numChannels);
+ int numSamples = numOutputSamples;
+ int remainingSamples = 0;
+
+ if (numSamples == 0 || maxSamples == 0) {
+ return 0;
+ }
+ if (numSamples > maxSamples) {
+ remainingSamples = numSamples - maxSamples;
+ numSamples = maxSamples;
+ }
+ for (int xSample = 0; xSample < numSamples * numChannels; xSample++) {
+ short sample = outputBuffer[xSample];
+ outBuffer[xSample << 1] = (byte) (sample & 0xff);
+ outBuffer[(xSample << 1) + 1] = (byte) (sample >> 8);
+ }
+ move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
+ numOutputSamples = remainingSamples;
+ return 2 * numSamples * numChannels;
+ }
+
+ // Force the sonic stream to generate output using whatever data it currently
+ // has. No extra delay will be added to the output, but flushing in the middle of
+ // words could introduce distortion.
+ public void flushStream() {
+ int remainingSamples = numInputSamples;
+ float s = speed / pitch;
+ float r = rate * pitch;
+ int expectedOutputSamples = numOutputSamples + (int) ((remainingSamples / s + numPitchSamples) / r + 0.5f);
+
+ // Add enough silence to flush both input and pitch buffers.
+ enlargeInputBufferIfNeeded(remainingSamples + 2 * maxRequired);
+ for (int xSample = 0; xSample < 2 * maxRequired * numChannels; xSample++) {
+ inputBuffer[remainingSamples * numChannels + xSample] = 0;
+ }
+ numInputSamples += 2 * maxRequired;
+ writeShortToStream(null, 0);
+ // Throw away any extra samples we generated due to the silence we added.
+ if (numOutputSamples > expectedOutputSamples) {
+ numOutputSamples = expectedOutputSamples;
+ }
+ // Empty input and pitch buffers.
+ numInputSamples = 0;
+ remainingInputToCopy = 0;
+ numPitchSamples = 0;
+ }
+
+ // Return the number of samples in the output buffer
+ public int samplesAvailable() {
+ return numOutputSamples;
+ }
+
+ // If skip is greater than one, average skip samples together and write them to
+ // the down-sample buffer. If numChannels is greater than one, mix the channels
+ // together as we down sample.
+ private void downSampleInput(short samples[],
+ int position,
+ int skip) {
+ int numSamples = maxRequired / skip;
+ int samplesPerValue = numChannels * skip;
+ int value;
+
+ position *= numChannels;
+ for (int i = 0; i < numSamples; i++) {
+ value = 0;
+ for (int j = 0; j < samplesPerValue; j++) {
+ value += samples[position + i * samplesPerValue + j];
+ }
+ value /= samplesPerValue;
+ downSampleBuffer[i] = (short) value;
+ }
+ }
+
+ // Find the best frequency match in the range, and given a sample skip multiple.
+ // For now, just find the pitch of the first channel. Note that retMinDiff and
+ // retMaxDiff are Int objects, which the caller will need to create with new.
+ private int findPitchPeriodInRange(short samples[],
+ int position,
+ int minPeriod,
+ int maxPeriod,
+ Integer retMinDiff,
+ Integer retMaxDiff) {
+ int bestPeriod = 0, worstPeriod = 255;
+ int minDiff = 1, maxDiff = 0;
+
+ position *= numChannels;
+ for (int period = minPeriod; period <= maxPeriod; period++) {
+ int diff = 0;
+ for (int i = 0; i < period; i++) {
+ short sVal = samples[position + i];
+ short pVal = samples[position + period + i];
+ diff += sVal >= pVal ? sVal - pVal : pVal - sVal;
+ }
+ /* Note that the highest number of samples we add into diff will be less
+ than 256, since we skip samples. Thus, diff is a 24 bit number, and
+ we can safely multiply by numSamples without overflow */
+ if (diff * bestPeriod < minDiff * period) {
+ minDiff = diff;
+ bestPeriod = period;
+ }
+ if (diff * worstPeriod > maxDiff * period) {
+ maxDiff = diff;
+ worstPeriod = period;
+ }
+ }
+ retMinDiff = minDiff / bestPeriod;
+ retMaxDiff = maxDiff / worstPeriod;
+ return bestPeriod;
+ }
+
+ // At abrupt ends of voiced words, we can have pitch periods that are better
+ // approximated by the previous pitch period estimate. Try to detect this case.
+ private boolean prevPeriodBetter(int period,
+ int minDiff,
+ int maxDiff,
+ boolean preferNewPeriod) {
+ if (minDiff == 0 || prevPeriod == 0) {
+ return false;
+ }
+ if (preferNewPeriod) {
+ if (maxDiff > minDiff * 3) {
+ // Got a reasonable match this period
+ return false;
+ }
+ if (minDiff * 2 <= prevMinDiff * 3) {
+ // Mismatch is not that much greater this period
+ return false;
+ }
+ } else {
+ if (minDiff <= prevMinDiff) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Find the pitch period. This is a critical step, and we may have to try
+ // multiple ways to get a good answer. This version uses AMDF. To improve
+ // speed, we down sample by an integer factor get in the 11KHz range, and then
+ // do it again with a narrower frequency range without down sampling
+ private int findPitchPeriod(short samples[],
+ int position,
+ boolean preferNewPeriod) {
+ Integer minDiff = new Integer(0);
+ Integer maxDiff = new Integer(0);
+ int period, retPeriod;
+ int skip = 1;
+
+ if (sampleRate > SONIC_AMDF_FREQ && quality == 0) {
+ skip = sampleRate / SONIC_AMDF_FREQ;
+ }
+ if (numChannels == 1 && skip == 1) {
+ period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod, minDiff, maxDiff);
+ } else {
+ downSampleInput(samples, position, skip);
+ period = findPitchPeriodInRange(downSampleBuffer, 0, minPeriod / skip,
+ maxPeriod / skip, minDiff, maxDiff);
+ if (skip != 1) {
+ period *= skip;
+ int minP = period - (skip << 2);
+ int maxP = period + (skip << 2);
+ if (minP < minPeriod) {
+ minP = minPeriod;
+ }
+ if (maxP > maxPeriod) {
+ maxP = maxPeriod;
+ }
+ if (numChannels == 1) {
+ period = findPitchPeriodInRange(samples, position, minP, maxP, minDiff, maxDiff);
+ } else {
+ downSampleInput(samples, position, 1);
+ period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP, minDiff, maxDiff);
+ }
+ }
+ }
+ if (prevPeriodBetter(period, minDiff, maxDiff, preferNewPeriod)) {
+ retPeriod = prevPeriod;
+ } else {
+ retPeriod = period;
+ }
+ prevMinDiff = minDiff;
+ prevPeriod = period;
+ return retPeriod;
+ }
+
+ // Overlap two sound segments, ramp the volume of one down, while ramping the
+ // other one from zero up, and add them, storing the result at the output.
+ private void overlapAdd(int numSamples,
+ int numChannels,
+ short out[],
+ int outPos,
+ short rampDown[],
+ int rampDownPos,
+ short rampUp[],
+ int rampUpPos) {
+ for (int i = 0; i < numChannels; i++) {
+ int o = outPos * numChannels + i;
+ int u = rampUpPos * numChannels + i;
+ int d = rampDownPos * numChannels + i;
+ for (int t = 0; t < numSamples; t++) {
+ out[o] = (short) ((rampDown[d] * (numSamples - t) + rampUp[u] * t) / numSamples);
+ o += numChannels;
+ d += numChannels;
+ u += numChannels;
+ }
+ }
+ }
+
+ // Overlap two sound segments, ramp the volume of one down, while ramping the
+ // other one from zero up, and add them, storing the result at the output.
+ private void overlapAddWithSeparation(int numSamples,
+ int numChannels,
+ int separation,
+ short out[],
+ int outPos,
+ short rampDown[],
+ int rampDownPos,
+ short rampUp[],
+ int rampUpPos) {
+ for (int i = 0; i < numChannels; i++) {
+ int o = outPos * numChannels + i;
+ int u = rampUpPos * numChannels + i;
+ int d = rampDownPos * numChannels + i;
+ for (int t = 0; t < numSamples + separation; t++) {
+ if (t < separation) {
+ out[o] = (short) (rampDown[d] * (numSamples - t) / numSamples);
+ d += numChannels;
+ } else if (t < numSamples) {
+ out[o] = (short) ((rampDown[d] * (numSamples - t) + rampUp[u] * (t - separation)) / numSamples);
+ d += numChannels;
+ u += numChannels;
+ } else {
+ out[o] = (short) (rampUp[u] * (t - separation) / numSamples);
+ u += numChannels;
+ }
+ o += numChannels;
+ }
+ }
+ }
+
+ // Just move the new samples in the output buffer to the pitch buffer
+ private void moveNewSamplesToPitchBuffer(int originalNumOutputSamples) {
+ int numSamples = numOutputSamples - originalNumOutputSamples;
+
+ if (numPitchSamples + numSamples > pitchBufferSize) {
+ pitchBufferSize += (pitchBufferSize >> 1) + numSamples;
+ pitchBuffer = resize(pitchBuffer, pitchBufferSize);
+ }
+ move(pitchBuffer, numPitchSamples, outputBuffer, originalNumOutputSamples, numSamples);
+ numOutputSamples = originalNumOutputSamples;
+ numPitchSamples += numSamples;
+ }
+
+ // Remove processed samples from the pitch buffer.
+ private void removePitchSamples(int numSamples) {
+ if (numSamples == 0) {
+ return;
+ }
+ move(pitchBuffer, 0, pitchBuffer, numSamples, numPitchSamples - numSamples);
+ numPitchSamples -= numSamples;
+ }
+
+ // Change the pitch. The latency this introduces could be reduced by looking at
+ // past samples to determine pitch, rather than future.
+ private void adjustPitch(int originalNumOutputSamples) {
+ int period, newPeriod, separation;
+ int position = 0;
+
+ if (numOutputSamples == originalNumOutputSamples) {
+ return;
+ }
+ moveNewSamplesToPitchBuffer(originalNumOutputSamples);
+ while (numPitchSamples - position >= maxRequired) {
+ period = findPitchPeriod(pitchBuffer, position, false);
+ newPeriod = (int) (period / pitch);
+ enlargeOutputBufferIfNeeded(newPeriod);
+ if (pitch >= 1.0f) {
+ overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer,
+ position, pitchBuffer, position + period - newPeriod);
+ } else {
+ separation = newPeriod - period;
+ overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples,
+ pitchBuffer, position, pitchBuffer, position);
+ }
+ numOutputSamples += newPeriod;
+ position += period;
+ }
+ removePitchSamples(position);
+ }
+
+ // Interpolate the new output sample.
+ private short interpolate(short in[],
+ int inPos,
+ int oldSampleRate,
+ int newSampleRate) {
+ short left = in[inPos * numChannels];
+ short right = in[inPos * numChannels + numChannels];
+ int position = newRatePosition * oldSampleRate;
+ int leftPosition = oldRatePosition * newSampleRate;
+ int rightPosition = (oldRatePosition + 1) * newSampleRate;
+ int ratio = rightPosition - position;
+ int width = rightPosition - leftPosition;
+
+ return (short) ((ratio * left + (width - ratio) * right) / width);
+ }
+
+ // Change the rate.
+ private void adjustRate(float rate,
+ int originalNumOutputSamples) {
+ int newSampleRate = (int) (sampleRate / rate);
+ int oldSampleRate = sampleRate;
+ int position;
+
+ // Set these values to help with the integer math
+ while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) {
+ newSampleRate >>= 1;
+ oldSampleRate >>= 1;
+ }
+ if (numOutputSamples == originalNumOutputSamples) {
+ return;
+ }
+ moveNewSamplesToPitchBuffer(originalNumOutputSamples);
+ // Leave at least one pitch sample in the buffer
+ for (position = 0; position < numPitchSamples - 1; position++) {
+ while ((oldRatePosition + 1) * newSampleRate > newRatePosition * oldSampleRate) {
+ enlargeOutputBufferIfNeeded(1);
+ for (int i = 0; i < numChannels; i++) {
+ outputBuffer[numOutputSamples * numChannels + i] = interpolate(pitchBuffer, position + i,
+ oldSampleRate, newSampleRate);
+ }
+ newRatePosition++;
+ numOutputSamples++;
+ }
+ oldRatePosition++;
+ if (oldRatePosition == oldSampleRate) {
+ oldRatePosition = 0;
+ if (newRatePosition != newSampleRate) {
+ System.out.printf("Assertion failed: newRatePosition != newSampleRate\n");
+ assert false;
+ }
+ newRatePosition = 0;
+ }
+ }
+ removePitchSamples(position);
+ }
+
+
+ // Skip over a pitch period, and copy period/speed samples to the output
+ private int skipPitchPeriod(short samples[],
+ int position,
+ float speed,
+ int period) {
+ int newSamples;
+
+ if (speed >= 2.0f) {
+ newSamples = (int) (period / (speed - 1.0f));
+ } else {
+ newSamples = period;
+ remainingInputToCopy = (int) (period * (2.0f - speed) / (speed - 1.0f));
+ }
+ enlargeOutputBufferIfNeeded(newSamples);
+ overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position,
+ samples, position + period);
+ numOutputSamples += newSamples;
+ return newSamples;
+ }
+
+ // Insert a pitch period, and determine how much input to copy directly.
+ private int insertPitchPeriod(short samples[],
+ int position,
+ float speed,
+ int period) {
+ int newSamples;
+
+ if (speed < 0.5f) {
+ newSamples = (int) (period * speed / (1.0f - speed));
+ } else {
+ newSamples = period;
+ remainingInputToCopy = (int) (period * (2.0f * speed - 1.0f) / (1.0f - speed));
+ }
+ enlargeOutputBufferIfNeeded(period + newSamples);
+ move(outputBuffer, numOutputSamples, samples, position, period);
+ overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples,
+ position + period, samples, position);
+ numOutputSamples += period + newSamples;
+ return newSamples;
+ }
+
+ // Resample as many pitch periods as we have buffered on the input. Return 0 if
+ // we fail to resize an input or output buffer. Also scale the output by the volume.
+ private void changeSpeed(float speed) {
+ int numSamples = numInputSamples;
+ int position = 0, period, newSamples;
+
+ if (numInputSamples < maxRequired) {
+ return;
+ }
+ do {
+ if (remainingInputToCopy > 0) {
+ newSamples = copyInputToOutput(position);
+ position += newSamples;
+ } else {
+ period = findPitchPeriod(inputBuffer, position, true);
+ if (speed > 1.0) {
+ newSamples = skipPitchPeriod(inputBuffer, position, speed, period);
+ position += period + newSamples;
+ } else {
+ newSamples = insertPitchPeriod(inputBuffer, position, speed, period);
+ position += newSamples;
+ }
+ }
+ } while (position + maxRequired <= numSamples);
+ removeInputSamples(position);
+ }
+
+ // Resample as many pitch periods as we have buffered on the input. Scale the output by the volume.
+ private void processStreamInput() {
+ int originalNumOutputSamples = numOutputSamples;
+ float s = speed / pitch;
+ float r = rate;
+
+ if (!useChordPitch) {
+ r *= pitch;
+ }
+ if (s > 1.00001 || s < 0.99999) {
+ changeSpeed(s);
+ } else {
+ copyToOutput(inputBuffer, 0, numInputSamples);
+ numInputSamples = 0;
+ }
+ if (useChordPitch && pitch != 1.0f) {
+ adjustPitch(originalNumOutputSamples);
+ } else if (r != 1.0f) {
+ adjustRate(r, originalNumOutputSamples);
+ }
+ if (volume != 1.0f) {
+ // Adjust output volume.
+ scaleSamples(outputBuffer, originalNumOutputSamples, numOutputSamples - originalNumOutputSamples,
+ volume);
+ }
+ }
+
+ // Write floating point data to the input buffer and process it.
+ public void writeFloatToStream(float samples[],
+ int numSamples) {
+ addFloatSamplesToInputBuffer(samples, numSamples);
+ processStreamInput();
+ }
+
+ // Write the data to the input stream, and process it.
+ public void writeShortToStream(short samples[],
+ int numSamples) {
+ addShortSamplesToInputBuffer(samples, numSamples);
+ processStreamInput();
+ }
+
+ // Simple wrapper around sonicWriteFloatToStream that does the unsigned byte to short
+ // conversion for you.
+ public void writeUnsignedByteToStream(byte samples[],
+ int numSamples) {
+ addUnsignedByteSamplesToInputBuffer(samples, numSamples);
+ processStreamInput();
+ }
+
+ // Simple wrapper around sonicWriteBytesToStream that does the byte to 16-bit LE conversion.
+ public void writeBytesToStream(byte inBuffer[],
+ int numBytes) {
+ addBytesToInputBuffer(inBuffer, numBytes);
+ processStreamInput();
+ }
+
+ // This is a non-stream oriented interface to just change the speed of a sound sample
+ public static int changeFloatSpeed(float samples[],
+ int numSamples,
+ float speed,
+ float pitch,
+ float rate,
+ float volume,
+ boolean useChordPitch,
+ int sampleRate,
+ int numChannels) {
+ Sonic stream = new Sonic(sampleRate, numChannels);
+
+ stream.setSpeed(speed);
+ stream.setPitch(pitch);
+ stream.setRate(rate);
+ stream.setVolume(volume);
+ stream.setChordPitch(useChordPitch);
+ stream.writeFloatToStream(samples, numSamples);
+ stream.flushStream();
+ numSamples = stream.samplesAvailable();
+ stream.readFloatFromStream(samples, numSamples);
+ return numSamples;
+ }
+
+ /* This is a non-stream oriented interface to just change the speed of a sound sample */
+ public int sonicChangeShortSpeed(short samples[],
+ int numSamples,
+ float speed,
+ float pitch,
+ float rate,
+ float volume,
+ boolean useChordPitch,
+ int sampleRate,
+ int numChannels) {
+ Sonic stream = new Sonic(sampleRate, numChannels);
+
+ stream.setSpeed(speed);
+ stream.setPitch(pitch);
+ stream.setRate(rate);
+ stream.setVolume(volume);
+ stream.setChordPitch(useChordPitch);
+ stream.writeShortToStream(samples, numSamples);
+ stream.flushStream();
+ numSamples = stream.samplesAvailable();
+ stream.readShortFromStream(samples, numSamples);
+ return numSamples;
+ }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..d8f14a1
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':library'