├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── aocate │ │ └── presto │ │ └── service │ │ ├── IDeathCallback_0_8.aidl │ │ ├── IOnBufferingUpdateListenerCallback_0_8.aidl │ │ ├── IOnCompletionListenerCallback_0_8.aidl │ │ ├── IOnErrorListenerCallback_0_8.aidl │ │ ├── IOnInfoListenerCallback_0_8.aidl │ │ ├── IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.aidl │ │ ├── IOnPreparedListenerCallback_0_8.aidl │ │ ├── IOnSeekCompleteListenerCallback_0_8.aidl │ │ ├── IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.aidl │ │ └── IPlayMedia_0_8.aidl │ └── java │ └── org │ ├── antennapod │ └── audio │ │ ├── AbstractAudioPlayer.java │ │ ├── AndroidAudioPlayer.java │ │ ├── DownMixer.java │ │ ├── MediaPlayer.java │ │ ├── ServiceBackedAudioPlayer.java │ │ ├── SonicAudioPlayer.java │ │ └── SonicAudioPlayerState.java │ └── vinuxproject │ └── sonic │ └── Sonic.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | /*/build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | gradle.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio 31 | .idea/ 32 | *.iml 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AntennaPod-AudioPlayer 2 | 3 | This is the repository for library code separated from the main repository for licensing compliance. 4 | 5 | ## License 6 | 7 | All code in this repository is licensed under the Apache License, Version 2.0. 8 | You can find the license text in the LICENSE file. 9 | 10 | ## Local testing 11 | 12 | **settings.gradle** 13 | ``` 14 | ... 15 | include ':aap' 16 | project(':aap').projectDir = new File('../AntennaPod-AudioPlayer/library') 17 | ``` 18 | 19 | **app/build.gradle** and **core/build.gradle** 20 | 21 | 22 | Edit both ``AntennaPod/app/build.gradle`` and ``AntennaPod/core/build.gradle`` 23 | ``` 24 | 25 | dependencies { 26 | .... 27 | compile project(":aap") 28 | } 29 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.6.3' 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | jcenter() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AntennaPod/AntennaPod-AudioPlayer/727f8191d420be14ac2bf0e9e17860884e0fbf59/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.library" 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | defaultConfig { 7 | minSdkVersion 16 8 | targetSdkVersion 28 9 | versionCode 200000 10 | versionName "2.0.0" 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -dontobfuscate 2 | -optimizationpasses 5 3 | -dontusemixedcaseclassnames 4 | -dontskipnonpubliclibraryclasses 5 | -dontpreverify 6 | -verbose 7 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 8 | 9 | # disable logging 10 | -assumenosideeffects class android.util.Log { 11 | public static boolean isLoggable(java.lang.String, int); 12 | public static *** v(...); 13 | public static *** i(...); 14 | public static *** w(...); 15 | public static *** d(...); 16 | public static *** e(...); 17 | } 18 | 19 | #Keep the R 20 | -keepclassmembers class **.R$* { 21 | public static ; 22 | } 23 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /library/src/main/aidl/com/aocate/presto/service/IDeathCallback_0_8.aidl: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.aocate.presto.service; 16 | 17 | oneway interface IDeathCallback_0_8 { 18 | } -------------------------------------------------------------------------------- /library/src/main/aidl/com/aocate/presto/service/IOnBufferingUpdateListenerCallback_0_8.aidl: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.aocate.presto.service; 16 | 17 | interface IOnBufferingUpdateListenerCallback_0_8 { 18 | void onBufferingUpdate(int percent); 19 | } -------------------------------------------------------------------------------- /library/src/main/aidl/com/aocate/presto/service/IOnCompletionListenerCallback_0_8.aidl: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.aocate.presto.service; 16 | 17 | interface IOnCompletionListenerCallback_0_8 { 18 | void onCompletion(); 19 | } -------------------------------------------------------------------------------- /library/src/main/aidl/com/aocate/presto/service/IOnErrorListenerCallback_0_8.aidl: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.aocate.presto.service; 16 | 17 | interface IOnErrorListenerCallback_0_8 { 18 | boolean onError(int what, int extra); 19 | } 20 | -------------------------------------------------------------------------------- /library/src/main/aidl/com/aocate/presto/service/IOnInfoListenerCallback_0_8.aidl: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.aocate.presto.service; 16 | 17 | interface IOnInfoListenerCallback_0_8 { 18 | boolean onInfo(int what, int extra); 19 | } -------------------------------------------------------------------------------- /library/src/main/aidl/com/aocate/presto/service/IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.aidl: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.aocate.presto.service; 16 | 17 | interface IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 { 18 | void onPitchAdjustmentAvailableChanged(boolean pitchAdjustmentAvailable); 19 | } -------------------------------------------------------------------------------- /library/src/main/aidl/com/aocate/presto/service/IOnPreparedListenerCallback_0_8.aidl: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.aocate.presto.service; 16 | 17 | interface IOnPreparedListenerCallback_0_8 { 18 | void onPrepared(); 19 | } -------------------------------------------------------------------------------- /library/src/main/aidl/com/aocate/presto/service/IOnSeekCompleteListenerCallback_0_8.aidl: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.aocate.presto.service; 16 | 17 | interface IOnSeekCompleteListenerCallback_0_8 { 18 | void onSeekComplete(); 19 | } -------------------------------------------------------------------------------- /library/src/main/aidl/com/aocate/presto/service/IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.aidl: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.aocate.presto.service; 16 | 17 | interface IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 { 18 | void onSpeedAdjustmentAvailableChanged(boolean speedAdjustmentAvailable); 19 | } -------------------------------------------------------------------------------- /library/src/main/aidl/com/aocate/presto/service/IPlayMedia_0_8.aidl: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.aocate.presto.service; 16 | 17 | import com.aocate.presto.service.IDeathCallback_0_8; 18 | import com.aocate.presto.service.IOnBufferingUpdateListenerCallback_0_8; 19 | import com.aocate.presto.service.IOnCompletionListenerCallback_0_8; 20 | import com.aocate.presto.service.IOnErrorListenerCallback_0_8; 21 | import com.aocate.presto.service.IOnPitchAdjustmentAvailableChangedListenerCallback_0_8; 22 | import com.aocate.presto.service.IOnPreparedListenerCallback_0_8; 23 | import com.aocate.presto.service.IOnSeekCompleteListenerCallback_0_8; 24 | import com.aocate.presto.service.IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8; 25 | import com.aocate.presto.service.IOnInfoListenerCallback_0_8; 26 | 27 | interface IPlayMedia_0_8 { 28 | boolean canSetPitch(long sessionId); 29 | boolean canSetSpeed(long sessionId); 30 | float getCurrentPitchStepsAdjustment(long sessionId); 31 | int getCurrentPosition(long sessionId); 32 | float getCurrentSpeedMultiplier(long sessionId); 33 | int getDuration(long sessionId); 34 | float getMaxSpeedMultiplier(long sessionId); 35 | float getMinSpeedMultiplier(long sessionId); 36 | int getVersionCode(); 37 | String getVersionName(); 38 | boolean isLooping(long sessionId); 39 | boolean isPlaying(long sessionId); 40 | void pause(long sessionId); 41 | void prepare(long sessionId); 42 | void prepareAsync(long sessionId); 43 | void registerOnBufferingUpdateCallback(long sessionId, IOnBufferingUpdateListenerCallback_0_8 cb); 44 | void registerOnCompletionCallback(long sessionId, IOnCompletionListenerCallback_0_8 cb); 45 | void registerOnErrorCallback(long sessionId, IOnErrorListenerCallback_0_8 cb); 46 | void registerOnInfoCallback(long sessionId, IOnInfoListenerCallback_0_8 cb); 47 | void registerOnPitchAdjustmentAvailableChangedCallback(long sessionId, IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 cb); 48 | void registerOnPreparedCallback(long sessionId, IOnPreparedListenerCallback_0_8 cb); 49 | void registerOnSeekCompleteCallback(long sessionId, IOnSeekCompleteListenerCallback_0_8 cb); 50 | void registerOnSpeedAdjustmentAvailableChangedCallback(long sessionId, IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 cb); 51 | void release(long sessionId); 52 | void reset(long sessionId); 53 | void seekTo(long sessionId, int msec); 54 | void setAudioStreamType(long sessionId, int streamtype); 55 | void setDataSourceString(long sessionId, String path); 56 | void setDataSourceUri(long sessionId, in Uri uri); 57 | void setEnableSpeedAdjustment(long sessionId, boolean enableSpeedAdjustment); 58 | void setLooping(long sessionId, boolean looping); 59 | void setPitchStepsAdjustment(long sessionId, float pitchSteps); 60 | void setPlaybackPitch(long sessionId, float f); 61 | void setPlaybackSpeed(long sessionId, float f); 62 | void setSpeedAdjustmentAlgorithm(long sessionId, int algorithm); 63 | void setVolume(long sessionId, float left, float right); 64 | void start(long sessionId); 65 | long startSession(IDeathCallback_0_8 cb); 66 | void stop(long sessionId); 67 | void unregisterOnBufferingUpdateCallback(long sessionId, IOnBufferingUpdateListenerCallback_0_8 cb); 68 | void unregisterOnCompletionCallback(long sessionId, IOnCompletionListenerCallback_0_8 cb); 69 | void unregisterOnErrorCallback(long sessionId, IOnErrorListenerCallback_0_8 cb); 70 | void unregisterOnInfoCallback(long sessionId, IOnInfoListenerCallback_0_8 cb); 71 | void unregisterOnPitchAdjustmentAvailableChangedCallback(long sessionId, IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 cb); 72 | void unregisterOnPreparedCallback(long sessionId, IOnPreparedListenerCallback_0_8 cb); 73 | void unregisterOnSeekCompleteCallback(long sessionId, IOnSeekCompleteListenerCallback_0_8 cb); 74 | void unregisterOnSpeedAdjustmentAvailableChangedCallback(long sessionId, IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 cb); 75 | } -------------------------------------------------------------------------------- /library/src/main/java/org/antennapod/audio/AbstractAudioPlayer.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package org.antennapod.audio; 16 | 17 | import android.content.Context; 18 | import android.net.Uri; 19 | import android.util.Log; 20 | 21 | import java.io.IOException; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import java.util.concurrent.locks.ReentrantLock; 25 | 26 | public abstract class AbstractAudioPlayer { 27 | 28 | private static final String MPI_TAG = "AbstractMediaPlayer"; 29 | protected final MediaPlayer owningMediaPlayer; 30 | protected final Context mContext; 31 | protected int muteOnPreparedCount = 0; 32 | protected int muteOnSeekCount = 0; 33 | private final String userAgent; 34 | 35 | public AbstractAudioPlayer(MediaPlayer owningMediaPlayer, Context context, String userAgent) { 36 | this.owningMediaPlayer = owningMediaPlayer; 37 | this.mContext = context; 38 | this.userAgent = userAgent; 39 | } 40 | 41 | public abstract int getAudioSessionId(); 42 | 43 | public abstract boolean canSetPitch(); 44 | 45 | public abstract boolean canSetSpeed(); 46 | 47 | public abstract boolean canDownmix(); 48 | 49 | public abstract float getCurrentPitchStepsAdjustment(); 50 | 51 | public abstract int getCurrentPosition(); 52 | 53 | public abstract float getCurrentSpeedMultiplier(); 54 | 55 | public abstract int getDuration(); 56 | 57 | public abstract float getMaxSpeedMultiplier(); 58 | 59 | public abstract float getMinSpeedMultiplier(); 60 | 61 | public abstract boolean isLooping(); 62 | 63 | public abstract boolean isPlaying(); 64 | 65 | public abstract void pause(); 66 | 67 | public abstract void prepare() throws IllegalStateException, IOException; 68 | 69 | public abstract void prepareAsync(); 70 | 71 | public abstract void release(); 72 | 73 | public abstract void reset(); 74 | 75 | public abstract void seekTo(int msec) throws IllegalStateException; 76 | 77 | public abstract void setAudioStreamType(int streamtype); 78 | 79 | public abstract void setDataSource(Context context, Uri uri) throws IllegalArgumentException, IllegalStateException, IOException; 80 | 81 | public abstract void setDataSource(String path) throws IllegalArgumentException, IllegalStateException, IOException; 82 | 83 | public abstract void setEnableSpeedAdjustment(boolean enableSpeedAdjustment); 84 | 85 | public abstract void setLooping(boolean loop); 86 | 87 | public abstract void setPitchStepsAdjustment(float pitchSteps); 88 | 89 | public abstract void setPlaybackPitch(float f); 90 | 91 | public abstract void setPlaybackSpeed(float f); 92 | 93 | public abstract void setDownmix(boolean enable); 94 | 95 | public abstract void setVolume(float leftVolume, float rightVolume); 96 | 97 | public abstract void setWakeMode(Context context, int mode); 98 | 99 | public abstract void start(); 100 | 101 | public abstract void stop(); 102 | 103 | protected final ReentrantLock lockMuteOnPreparedCount = new ReentrantLock(); 104 | 105 | public void muteNextOnPrepare() { 106 | lockMuteOnPreparedCount.lock(); 107 | Log.d(MPI_TAG, "muteNextOnPrepare()"); 108 | try { 109 | this.muteOnPreparedCount++; 110 | } finally { 111 | lockMuteOnPreparedCount.unlock(); 112 | } 113 | } 114 | 115 | protected final ReentrantLock lockMuteOnSeekCount = new ReentrantLock(); 116 | 117 | public void muteNextSeek() { 118 | lockMuteOnSeekCount.lock(); 119 | Log.d(MPI_TAG, "muteNextOnSeek()"); 120 | try { 121 | this.muteOnSeekCount++; 122 | } finally { 123 | lockMuteOnSeekCount.unlock(); 124 | } 125 | } 126 | 127 | protected Map getHeaders() { 128 | Map headerMap = new HashMap<>(); 129 | headerMap.put("User-Agent", userAgent); 130 | return headerMap; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /library/src/main/java/org/antennapod/audio/AndroidAudioPlayer.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package org.antennapod.audio; 16 | 17 | import android.content.Context; 18 | import android.media.MediaPlayer; 19 | import android.media.PlaybackParams; 20 | import android.net.Uri; 21 | import android.os.Build; 22 | import android.util.Log; 23 | 24 | import java.io.IOException; 25 | import java.lang.reflect.InvocationTargetException; 26 | import java.lang.reflect.Method; 27 | import java.util.Map; 28 | 29 | public class AndroidAudioPlayer extends AbstractAudioPlayer { 30 | 31 | private final static String AMP_TAG = "AndroidMediaPlayer"; 32 | 33 | MediaPlayer mp = null; 34 | 35 | private final MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { 36 | public void onBufferingUpdate(MediaPlayer mp, int percent) { 37 | if (owningMediaPlayer != null) { 38 | owningMediaPlayer.lock.lock(); 39 | try { 40 | if ((owningMediaPlayer.onBufferingUpdateListener != null) 41 | && (owningMediaPlayer.mpi == AndroidAudioPlayer.this)) { 42 | owningMediaPlayer.onBufferingUpdateListener.onBufferingUpdate(owningMediaPlayer, percent); 43 | } 44 | } finally { 45 | owningMediaPlayer.lock.unlock(); 46 | } 47 | } 48 | 49 | } 50 | }; 51 | 52 | private final MediaPlayer.OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() { 53 | public void onCompletion(MediaPlayer mp) { 54 | Log.d(AMP_TAG, "onCompletionListener being called"); 55 | if (owningMediaPlayer != null) { 56 | owningMediaPlayer.lock.lock(); 57 | try { 58 | if (owningMediaPlayer.onCompletionListener != null) { 59 | owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer); 60 | } 61 | } finally { 62 | owningMediaPlayer.lock.unlock(); 63 | } 64 | } 65 | } 66 | }; 67 | 68 | private final MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() { 69 | public boolean onError(MediaPlayer mp, int what, int extra) { 70 | // Once we're in errored state, any received messages are going to be junked 71 | if (owningMediaPlayer != null) { 72 | owningMediaPlayer.lock.lock(); 73 | try { 74 | if (owningMediaPlayer.onErrorListener != null) { 75 | return owningMediaPlayer.onErrorListener.onError(owningMediaPlayer, what, extra); 76 | } 77 | } finally { 78 | owningMediaPlayer.lock.unlock(); 79 | } 80 | } 81 | return false; 82 | } 83 | }; 84 | 85 | private final MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener() { 86 | public boolean onInfo(MediaPlayer mp, int what, int extra) { 87 | if (owningMediaPlayer != null) { 88 | owningMediaPlayer.lock.lock(); 89 | try { 90 | if ((owningMediaPlayer.onInfoListener != null) 91 | && (owningMediaPlayer.mpi == AndroidAudioPlayer.this)) { 92 | return owningMediaPlayer.onInfoListener.onInfo(owningMediaPlayer, what, extra); 93 | } 94 | } finally { 95 | owningMediaPlayer.lock.unlock(); 96 | } 97 | } 98 | return false; 99 | } 100 | }; 101 | 102 | // We have to assign this.onPreparedListener because the 103 | // onPreparedListener in owningMediaPlayer sets the state 104 | // to PREPARED. Due to prepareAsync, that's the only 105 | // reasonable place to do it 106 | // The others it just didn't make sense to have a setOnXListener that didn't use the parameter 107 | private final MediaPlayer.OnPreparedListener onPreparedListener = new MediaPlayer.OnPreparedListener() { 108 | public void onPrepared(MediaPlayer mp) { 109 | Log.d(AMP_TAG, "Calling onPreparedListener.onPrepared()"); 110 | if (AndroidAudioPlayer.this.owningMediaPlayer != null) { 111 | AndroidAudioPlayer.this.lockMuteOnPreparedCount.lock(); 112 | try { 113 | if (AndroidAudioPlayer.this.muteOnPreparedCount > 0) { 114 | AndroidAudioPlayer.this.muteOnPreparedCount--; 115 | } else { 116 | AndroidAudioPlayer.this.muteOnPreparedCount = 0; 117 | Log.d(AMP_TAG, "Invoking AndroidMediaPlayer.this.owningMediaPlayer.onPreparedListener.onPrepared"); 118 | AndroidAudioPlayer.this.owningMediaPlayer.onPreparedListener.onPrepared(AndroidAudioPlayer.this.owningMediaPlayer); 119 | } 120 | } finally { 121 | AndroidAudioPlayer.this.lockMuteOnPreparedCount.unlock(); 122 | } 123 | if (owningMediaPlayer.mpi != AndroidAudioPlayer.this) { 124 | Log.d(AMP_TAG, "owningMediaPlayer has changed implementation"); 125 | } 126 | } 127 | } 128 | }; 129 | 130 | private final MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() { 131 | public void onSeekComplete(MediaPlayer mp) { 132 | if (owningMediaPlayer != null) { 133 | owningMediaPlayer.lock.lock(); 134 | try { 135 | lockMuteOnSeekCount.lock(); 136 | try { 137 | if (AndroidAudioPlayer.this.muteOnSeekCount > 0) { 138 | AndroidAudioPlayer.this.muteOnSeekCount--; 139 | } else { 140 | AndroidAudioPlayer.this.muteOnSeekCount = 0; 141 | if (AndroidAudioPlayer.this.owningMediaPlayer.onSeekCompleteListener != null) { 142 | owningMediaPlayer.onSeekCompleteListener.onSeekComplete(owningMediaPlayer); 143 | } 144 | } 145 | } finally { 146 | lockMuteOnSeekCount.unlock(); 147 | } 148 | } finally { 149 | owningMediaPlayer.lock.unlock(); 150 | } 151 | } 152 | } 153 | }; 154 | 155 | public AndroidAudioPlayer(org.antennapod.audio.MediaPlayer owningMediaPlayer, Context context, String userAgent) { 156 | super(owningMediaPlayer, context, userAgent); 157 | 158 | mp = new MediaPlayer(); 159 | if (mp == null) { 160 | throw new IllegalStateException("Did not instantiate MediaPlayer successfully"); 161 | } 162 | 163 | mp.setOnBufferingUpdateListener(this.onBufferingUpdateListener); 164 | mp.setOnCompletionListener(this.onCompletionListener); 165 | mp.setOnErrorListener(this.onErrorListener); 166 | mp.setOnInfoListener(this.onInfoListener); 167 | Log.d(AMP_TAG, "Setting prepared listener to this.onPreparedListener"); 168 | mp.setOnPreparedListener(this.onPreparedListener); 169 | mp.setOnSeekCompleteListener(this.onSeekCompleteListener); 170 | } 171 | 172 | @Override 173 | public int getAudioSessionId() { 174 | return mp.getAudioSessionId(); 175 | } 176 | 177 | @Override 178 | public boolean canSetPitch() { 179 | return Build.VERSION.SDK_INT >= 23; 180 | } 181 | 182 | @Override 183 | public boolean canSetSpeed() { 184 | return Build.VERSION.SDK_INT >= 23; 185 | } 186 | 187 | @Override 188 | public float getCurrentPitchStepsAdjustment() { 189 | return 0; 190 | } 191 | 192 | @Override 193 | public boolean canDownmix() { 194 | return false; 195 | } 196 | 197 | @Override 198 | public int getCurrentPosition() { 199 | owningMediaPlayer.lock.lock(); 200 | try { 201 | return mp.getCurrentPosition(); 202 | } catch (IllegalStateException e) { 203 | return -1; 204 | } finally { 205 | owningMediaPlayer.lock.unlock(); 206 | } 207 | } 208 | 209 | @Override 210 | public float getCurrentSpeedMultiplier() { 211 | return 1f; 212 | } 213 | 214 | @Override 215 | public int getDuration() { 216 | owningMediaPlayer.lock.lock(); 217 | try { 218 | return mp.getDuration(); 219 | } catch (IllegalStateException e) { 220 | return -1; 221 | } finally { 222 | owningMediaPlayer.lock.unlock(); 223 | } 224 | } 225 | 226 | @Override 227 | public float getMaxSpeedMultiplier() { 228 | return 1f; 229 | } 230 | 231 | @Override 232 | public float getMinSpeedMultiplier() { 233 | return 1f; 234 | } 235 | 236 | @Override 237 | public boolean isLooping() { 238 | owningMediaPlayer.lock.lock(); 239 | try { 240 | return mp.isLooping(); 241 | } catch (IllegalStateException e) { 242 | return false; 243 | } finally { 244 | owningMediaPlayer.lock.unlock(); 245 | } 246 | } 247 | 248 | @Override 249 | public boolean isPlaying() { 250 | owningMediaPlayer.lock.lock(); 251 | try { 252 | return mp.isPlaying(); 253 | } catch (IllegalStateException e) { 254 | return false; 255 | } finally { 256 | owningMediaPlayer.lock.unlock(); 257 | } 258 | } 259 | 260 | @Override 261 | public void pause() { 262 | owningMediaPlayer.lock.lock(); 263 | try { 264 | mp.pause(); 265 | } finally { 266 | owningMediaPlayer.lock.unlock(); 267 | } 268 | } 269 | 270 | @Override 271 | public void prepare() throws IllegalStateException, IOException { 272 | owningMediaPlayer.lock.lock(); 273 | Log.d(AMP_TAG, "prepare()"); 274 | try { 275 | mp.prepare(); 276 | Log.d(AMP_TAG, "Finish prepare()"); 277 | } finally { 278 | owningMediaPlayer.lock.unlock(); 279 | } 280 | } 281 | 282 | @Override 283 | public void prepareAsync() { 284 | mp.prepareAsync(); 285 | } 286 | 287 | @Override 288 | public void release() { 289 | owningMediaPlayer.lock.lock(); 290 | try { 291 | if (mp != null) { 292 | Log.d(AMP_TAG, "mp.release()"); 293 | mp.release(); 294 | } 295 | } catch (IllegalStateException e) { 296 | // ignore 297 | } finally { 298 | owningMediaPlayer.lock.unlock(); 299 | } 300 | } 301 | 302 | @Override 303 | public void reset() { 304 | owningMediaPlayer.lock.lock(); 305 | try { 306 | mp.reset(); 307 | } catch (IllegalStateException e) { 308 | Log.e(AMP_TAG, Log.getStackTraceString(e)); 309 | } finally { 310 | owningMediaPlayer.lock.unlock(); 311 | } 312 | } 313 | 314 | @Override 315 | public void seekTo(int msec) throws IllegalStateException { 316 | owningMediaPlayer.lock.lock(); 317 | try { 318 | mp.setOnSeekCompleteListener(this.onSeekCompleteListener); 319 | mp.seekTo(msec); 320 | } finally { 321 | owningMediaPlayer.lock.unlock(); 322 | } 323 | } 324 | 325 | @Override 326 | public void setAudioStreamType(int streamtype) { 327 | owningMediaPlayer.lock.lock(); 328 | try { 329 | mp.setAudioStreamType(streamtype); 330 | } finally { 331 | owningMediaPlayer.lock.unlock(); 332 | } 333 | } 334 | 335 | @Override 336 | public void setDataSource(Context context, Uri uri) 337 | throws IllegalArgumentException, IllegalStateException, IOException { 338 | owningMediaPlayer.lock.lock(); 339 | try { 340 | Log.d(AMP_TAG, "setDataSource(context, " + uri.toString() + ")"); 341 | mp.setDataSource(context, uri, getHeaders()); 342 | } finally { 343 | owningMediaPlayer.lock.unlock(); 344 | } 345 | } 346 | 347 | @Override 348 | public void setDataSource(String path) throws IllegalArgumentException, 349 | IllegalStateException, IOException { 350 | owningMediaPlayer.lock.lock(); 351 | try { 352 | Log.d(AMP_TAG, "setDataSource(" + path + ")"); 353 | 354 | // setDataSource(String, Map) is annotated @hide. 355 | // Hack around with reflection to call the method. 356 | try { 357 | Method method = mp.getClass().getMethod("setDataSource", String.class, Map.class); 358 | method.invoke(mp, path, getHeaders()); 359 | return; 360 | } catch (NoSuchMethodException e) { 361 | e.printStackTrace(); 362 | } catch (IllegalAccessException e) { 363 | e.printStackTrace(); 364 | } catch (InvocationTargetException e) { 365 | e.printStackTrace(); 366 | } 367 | // Fall-back without headers 368 | mp.setDataSource(path); 369 | } finally { 370 | owningMediaPlayer.lock.unlock(); 371 | } 372 | } 373 | 374 | @Override 375 | public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) { 376 | // Can't! 377 | } 378 | 379 | @Override 380 | public void setLooping(boolean loop) { 381 | owningMediaPlayer.lock.lock(); 382 | try { 383 | mp.setLooping(loop); 384 | } finally { 385 | owningMediaPlayer.lock.unlock(); 386 | } 387 | } 388 | 389 | @Override 390 | public void setPitchStepsAdjustment(float pitchSteps) { 391 | if (Build.VERSION.SDK_INT < 23) { 392 | return; 393 | } 394 | PlaybackParams params = mp.getPlaybackParams(); 395 | params.setPitch(params.getPitch() + pitchSteps); 396 | mp.setPlaybackParams(params); 397 | } 398 | 399 | @Override 400 | public void setPlaybackPitch(float f) { 401 | Log.d(AMP_TAG, "setPlaybackPitch(" + f + ")"); 402 | if (Build.VERSION.SDK_INT < 23) { 403 | return; 404 | } 405 | PlaybackParams params = mp.getPlaybackParams(); 406 | params.setPitch(f); 407 | mp.setPlaybackParams(params); 408 | } 409 | 410 | @Override 411 | public void setPlaybackSpeed(float f) { 412 | Log.d(AMP_TAG, "setPlaybackSpeed(" + f + ")"); 413 | if (Build.VERSION.SDK_INT < 23) { 414 | return; 415 | } 416 | PlaybackParams params = mp.getPlaybackParams(); 417 | params.setSpeed(f); 418 | boolean isPaused = this.owningMediaPlayer.state == org.antennapod.audio.MediaPlayer.State.PAUSED; 419 | mp.setPlaybackParams(params); 420 | if (isPaused) { 421 | mp.pause(); 422 | } 423 | } 424 | 425 | @Override 426 | public void setDownmix(boolean enable) { 427 | return; 428 | } 429 | 430 | @Override 431 | public void setVolume(float leftVolume, float rightVolume) { 432 | owningMediaPlayer.lock.lock(); 433 | try { 434 | mp.setVolume(leftVolume, rightVolume); 435 | } finally { 436 | owningMediaPlayer.lock.unlock(); 437 | } 438 | } 439 | 440 | @Override 441 | public void setWakeMode(Context context, int mode) { 442 | owningMediaPlayer.lock.lock(); 443 | try { 444 | if (mode != 0) { 445 | mp.setWakeMode(context, mode); 446 | } 447 | } finally { 448 | owningMediaPlayer.lock.unlock(); 449 | } 450 | } 451 | 452 | @Override 453 | public void start() { 454 | owningMediaPlayer.lock.lock(); 455 | try { 456 | mp.start(); 457 | } finally { 458 | owningMediaPlayer.lock.unlock(); 459 | } 460 | } 461 | 462 | @Override 463 | public void stop() { 464 | owningMediaPlayer.lock.lock(); 465 | try { 466 | mp.stop(); 467 | } finally { 468 | owningMediaPlayer.lock.unlock(); 469 | } 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /library/src/main/java/org/antennapod/audio/DownMixer.java: -------------------------------------------------------------------------------- 1 | package org.antennapod.audio; 2 | 3 | class DownMixer { 4 | 5 | static void downMix(byte[] modifiedSamples) { 6 | for (int i = 0; (i + 3) < modifiedSamples.length; i += 4) { 7 | short left = (short) ((modifiedSamples[i] & 0xff) | (modifiedSamples[i + 1] << 8)); 8 | short right = (short) ((modifiedSamples[i + 2] & 0xff) | (modifiedSamples[i + 3] << 8)); 9 | short value = (short) (0.5 * left + 0.5 * right); 10 | 11 | modifiedSamples[i] = (byte) (value & 0xff); 12 | modifiedSamples[i + 1] = (byte) (value >> 8); 13 | modifiedSamples[i + 2] = (byte) (value & 0xff); 14 | modifiedSamples[i + 3] = (byte) (value >> 8); 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /library/src/main/java/org/antennapod/audio/MediaPlayer.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Aocate, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package org.antennapod.audio; 16 | 17 | import android.content.ComponentName; 18 | import android.content.Context; 19 | import android.content.Intent; 20 | import android.content.ServiceConnection; 21 | import android.content.pm.PackageManager; 22 | import android.content.pm.ResolveInfo; 23 | import android.media.AudioManager; 24 | import android.net.Uri; 25 | import android.os.Build; 26 | import android.os.Handler; 27 | import android.os.IBinder; 28 | import android.os.Message; 29 | import android.util.Log; 30 | 31 | import java.io.IOException; 32 | import java.util.List; 33 | import java.util.concurrent.locks.ReentrantLock; 34 | 35 | public class MediaPlayer { 36 | 37 | public static final String TAG = "MediaPlayer"; 38 | 39 | public interface OnBufferingUpdateListener { 40 | void onBufferingUpdate(MediaPlayer arg0, int percent); 41 | } 42 | 43 | public interface OnCompletionListener { 44 | void onCompletion(MediaPlayer arg0); 45 | } 46 | 47 | public interface OnErrorListener { 48 | boolean onError(MediaPlayer arg0, int what, int extra); 49 | } 50 | 51 | public interface OnInfoListener { 52 | boolean onInfo(MediaPlayer arg0, int what, int extra); 53 | } 54 | 55 | public interface OnPitchAdjustmentAvailableChangedListener { 56 | /** 57 | * @param arg0 The owning media player 58 | * @param pitchAdjustmentAvailable True if pitch adjustment is available, false if not 59 | */ 60 | void onPitchAdjustmentAvailableChanged(MediaPlayer arg0, boolean pitchAdjustmentAvailable); 61 | } 62 | 63 | public interface OnPreparedListener { 64 | void onPrepared(MediaPlayer arg0); 65 | } 66 | 67 | public interface OnSeekCompleteListener { 68 | void onSeekComplete(MediaPlayer arg0); 69 | } 70 | 71 | public interface OnSpeedAdjustmentAvailableChangedListener { 72 | /** 73 | * @param arg0 The owning media player 74 | * @param speedAdjustmentAvailable True if speed adjustment is available, false if not 75 | */ 76 | void onSpeedAdjustmentAvailableChanged( 77 | MediaPlayer arg0, boolean speedAdjustmentAvailable); 78 | } 79 | 80 | public enum State { 81 | IDLE, INITIALIZED, PREPARED, STARTED, PAUSED, STOPPED, PREPARING, PLAYBACK_COMPLETED, END, ERROR 82 | } 83 | 84 | private final static Uri SPEED_ADJUSTMENT_MARKET_URI = Uri.parse("market://details?id=com.aocate.presto"); 85 | 86 | private static Intent prestoMarketIntent = null; 87 | 88 | public static final int MEDIA_ERROR_SERVER_DIED = android.media.MediaPlayer.MEDIA_ERROR_SERVER_DIED; 89 | public static final int MEDIA_ERROR_UNKNOWN = android.media.MediaPlayer.MEDIA_ERROR_UNKNOWN; 90 | public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = android.media.MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK; 91 | 92 | /** 93 | * Indicates whether the specified action can be used as an intent. This 94 | * method queries the package manager for installed packages that can 95 | * respond to an intent with the specified action. If no suitable package is 96 | * found, this method returns false. 97 | * 98 | * @param context The application's environment. 99 | * @param action The Intent action to check for availability. 100 | * @return True if an Intent with the specified action can be sent and 101 | * responded to, false otherwise. 102 | */ 103 | public static boolean isIntentAvailable(Context context, String action) { 104 | final PackageManager packageManager = context.getPackageManager(); 105 | final Intent intent = new Intent(action); 106 | List list = packageManager.queryIntentServices(intent, 107 | PackageManager.MATCH_DEFAULT_ONLY); 108 | return list.size() > 0; 109 | } 110 | 111 | /** 112 | * Returns an explicit Intent for a service that accepts the given Intent 113 | * or null if no such service was found. 114 | * 115 | * @param context The application's environment. 116 | * @param action The Intent action to check for availability. 117 | * @return The explicit service Intent or null if no service was found. 118 | */ 119 | public static Intent getPrestoServiceIntent(Context context, String action) { 120 | final PackageManager packageManager = context.getPackageManager(); 121 | final Intent actionIntent = new Intent(action); 122 | List list = packageManager.queryIntentServices(actionIntent, 123 | PackageManager.MATCH_DEFAULT_ONLY); 124 | if (list.size() > 0) { 125 | ResolveInfo first = list.get(0); 126 | if (first.serviceInfo != null) { 127 | Intent intent = new Intent(); 128 | intent.setComponent(new ComponentName(first.serviceInfo.packageName, 129 | first.serviceInfo.name)); 130 | Log.i(TAG, "Returning intent:" + intent.toString()); 131 | return intent; 132 | } else { 133 | Log.e(TAG, "Found service that accepts " + action + ", but serviceInfo was null"); 134 | return null; 135 | } 136 | } else { 137 | return null; 138 | } 139 | } 140 | 141 | /** 142 | * Indicates whether the Presto library is installed 143 | * 144 | * @param context The context to use to query the package manager. 145 | * @return True if the Presto library is installed, false if not. 146 | */ 147 | public static boolean isPrestoLibraryInstalled(Context context) { 148 | return isIntentAvailable(context, ServiceBackedAudioPlayer.INTENT_NAME); 149 | } 150 | 151 | /** 152 | * Return an Intent that opens the Android Market page for the speed 153 | * alteration library 154 | * 155 | * @return The Intent for the Presto library on the Android Market 156 | */ 157 | public static Intent getPrestoMarketIntent() { 158 | if (prestoMarketIntent == null) { 159 | prestoMarketIntent = new Intent(Intent.ACTION_VIEW, SPEED_ADJUSTMENT_MARKET_URI); 160 | } 161 | return prestoMarketIntent; 162 | } 163 | 164 | /** 165 | * Open the Android Market page for the Presto library 166 | * 167 | * @param context The context from which to open the Android Market page 168 | */ 169 | public static void openPrestoMarketIntent(Context context) { 170 | context.startActivity(getPrestoMarketIntent()); 171 | } 172 | 173 | private static final String MP_TAG = "ReplacementMediaPlayer"; 174 | 175 | private static final double PITCH_STEP_CONSTANT = 1.0594630943593; 176 | 177 | private AndroidAudioPlayer amp = null; 178 | private ServiceBackedAudioPlayer sbmp = null; 179 | private SonicAudioPlayer smp = null; 180 | 181 | // This is whether speed adjustment should be enabled (by the Service) 182 | // To avoid the Service entirely, set useService to false 183 | private boolean enableSpeedAdjustment = true; 184 | private int lastKnownPosition = 0; 185 | // In some cases, we're going to have to replace the 186 | // android.media.MediaPlayer on the fly, and we don't want to touch the 187 | // wrong media player, so lock it way too much. 188 | final ReentrantLock lock = new ReentrantLock(); 189 | private int mAudioStreamType = AudioManager.STREAM_MUSIC; 190 | private final Context mContext; 191 | private boolean mIsLooping = false; 192 | private float mLeftVolume = 1f; 193 | private float mPitchStepsAdjustment = 0f; 194 | private float mRightVolume = 1f; 195 | private float mSpeedMultiplier = 1f; 196 | private int mWakeMode = 0; 197 | AbstractAudioPlayer mpi = null; 198 | private boolean pitchAdjustmentAvailable = false; 199 | private boolean speedAdjustmentAvailable = false; 200 | private final String userAgent; 201 | 202 | private Handler mServiceDisconnectedHandler = null; 203 | 204 | // Some parts of state cannot be found by calling MediaPlayerImpl functions, 205 | // so store our own state. This also helps copy state when changing 206 | // implementations 207 | State state = State.INITIALIZED; 208 | private String stringDataSource = null; 209 | private Uri uriDataSource = null; 210 | private boolean useService = false; 211 | 212 | // Naming Convention for Listeners 213 | // Most listeners can both be set by clients and called by MediaPlayImpls 214 | // There are a few that have to do things in this class as well as calling 215 | // the function. In all cases, onX is what is called by MediaPlayerImpl 216 | // If there is work to be done in this class, then the listener that is 217 | // set by setX is X (with the first letter lowercase). 218 | OnBufferingUpdateListener onBufferingUpdateListener = null; 219 | OnCompletionListener onCompletionListener = null; 220 | OnErrorListener onErrorListener = null; 221 | OnInfoListener onInfoListener = null; 222 | 223 | // Special case. Pitch adjustment ceases to be available when we switch 224 | // to the android.media.MediaPlayer (though it is not guaranteed to be 225 | // available when using the ServiceBackedMediaPlayer) 226 | OnPitchAdjustmentAvailableChangedListener onPitchAdjustmentAvailableChangedListener = new OnPitchAdjustmentAvailableChangedListener() { 227 | public void onPitchAdjustmentAvailableChanged(MediaPlayer arg0, 228 | boolean pitchAdjustmentAvailable) { 229 | lock.lock(); 230 | try { 231 | Log.d(MP_TAG, "onPitchAdjustmentAvailableChangedListener.onPitchAdjustmentAvailableChanged being called"); 232 | if (MediaPlayer.this.pitchAdjustmentAvailable != pitchAdjustmentAvailable) { 233 | Log.d(MP_TAG, "Pitch adjustment state has changed from " 234 | + MediaPlayer.this.pitchAdjustmentAvailable 235 | + " to " + pitchAdjustmentAvailable); 236 | MediaPlayer.this.pitchAdjustmentAvailable = pitchAdjustmentAvailable; 237 | if (MediaPlayer.this.pitchAdjustmentAvailableChangedListener != null) { 238 | MediaPlayer.this.pitchAdjustmentAvailableChangedListener 239 | .onPitchAdjustmentAvailableChanged(arg0, pitchAdjustmentAvailable); 240 | } 241 | } 242 | } finally { 243 | lock.unlock(); 244 | } 245 | } 246 | }; 247 | private OnPitchAdjustmentAvailableChangedListener pitchAdjustmentAvailableChangedListener = null; 248 | 249 | final MediaPlayer.OnPreparedListener onPreparedListener = new MediaPlayer.OnPreparedListener() { 250 | public void onPrepared(MediaPlayer arg0) { 251 | Log.d(MP_TAG, "onPreparedListener 242 setting state to PREPARED"); 252 | MediaPlayer.this.state = State.PREPARED; 253 | if (MediaPlayer.this.preparedListener != null) { 254 | Log.d(MP_TAG, "Calling preparedListener"); 255 | MediaPlayer.this.preparedListener.onPrepared(arg0); 256 | } 257 | Log.d(MP_TAG, "Wrap up onPreparedListener"); 258 | } 259 | }; 260 | 261 | private OnPreparedListener preparedListener = null; 262 | OnSeekCompleteListener onSeekCompleteListener = null; 263 | 264 | // Special case. Speed adjustment ceases to be available when we switch 265 | // to the android.media.MediaPlayer (though it is not guaranteed to be 266 | // available when using the ServiceBackedMediaPlayer) 267 | OnSpeedAdjustmentAvailableChangedListener onSpeedAdjustmentAvailableChangedListener = new OnSpeedAdjustmentAvailableChangedListener() { 268 | public void onSpeedAdjustmentAvailableChanged(MediaPlayer arg0, 269 | boolean speedAdjustmentAvailable) { 270 | lock.lock(); 271 | try { 272 | Log.d(MP_TAG, "onSpeedAdjustmentAvailableChangedListener.onSpeedAdjustmentAvailableChanged being called"); 273 | if (MediaPlayer.this.speedAdjustmentAvailable != speedAdjustmentAvailable) { 274 | Log.d(MP_TAG, "Speed adjustment state has changed from " 275 | + MediaPlayer.this.speedAdjustmentAvailable 276 | + " to " + speedAdjustmentAvailable); 277 | MediaPlayer.this.speedAdjustmentAvailable = speedAdjustmentAvailable; 278 | if (MediaPlayer.this.speedAdjustmentAvailableChangedListener != null) { 279 | MediaPlayer.this.speedAdjustmentAvailableChangedListener 280 | .onSpeedAdjustmentAvailableChanged(arg0, speedAdjustmentAvailable); 281 | } 282 | } 283 | } finally { 284 | lock.unlock(); 285 | } 286 | } 287 | }; 288 | private OnSpeedAdjustmentAvailableChangedListener speedAdjustmentAvailableChangedListener = null; 289 | 290 | public MediaPlayer(final Context context, boolean useService, String userAgent) { 291 | this.mContext = context; 292 | this.useService = useService; 293 | this.userAgent = userAgent; 294 | 295 | // So here's the major problem 296 | // Sometimes the service won't exist or won't be connected, 297 | // so start with an android.media.MediaPlayer, and when 298 | // the service is connected, use that from then on 299 | this.mpi = this.amp = new AndroidAudioPlayer(this, context, userAgent); 300 | if (Build.VERSION.SDK_INT >= 16) { 301 | this.smp = new SonicAudioPlayer(this, context, userAgent); 302 | this.smp.setDownMix(downmix()); 303 | } 304 | 305 | // setupMpi will go get the Service, if it can, then bring that 306 | // implementation into sync 307 | Log.d(MP_TAG, "setupMpi"); 308 | setupMpi(context); 309 | } 310 | 311 | protected boolean useSonic() { 312 | return false; 313 | } 314 | 315 | protected boolean downmix() { 316 | return false; 317 | } 318 | 319 | private boolean invalidServiceConnectionConfiguration() { 320 | if (smp != null) { 321 | boolean usingSonic = this.mpi instanceof SonicAudioPlayer; 322 | if ((usingSonic && !useSonic()) || (!usingSonic && useSonic())) { 323 | return true; 324 | } 325 | } 326 | if (!(this.mpi instanceof ServiceBackedAudioPlayer)) { 327 | if (this.useService && isPrestoLibraryInstalled()) { 328 | // In this case, the Presto library has been installed 329 | // or something while playing sound 330 | // We could be using the service, but we're not 331 | Log.d(MP_TAG, "We could be using the service, but we're not"); 332 | return true; 333 | } 334 | // If useService is false, then we shouldn't be using the SBMP 335 | // If the Presto library isn't installed, ditto 336 | Log.d(MP_TAG, "this.mpi is not a ServiceBackedMediaPlayer, but we couldn't use it anyway"); 337 | return false; 338 | } else { 339 | if (BuildConfig.DEBUG && !(this.mpi instanceof ServiceBackedAudioPlayer)) 340 | throw new AssertionError(); 341 | if (this.useService && isPrestoLibraryInstalled()) { 342 | // We should be using the service, and we are. Great! 343 | Log.d(MP_TAG, "We could be using a ServiceBackedMediaPlayer and we are"); 344 | return false; 345 | } 346 | // We're trying to use the service when we shouldn't, 347 | // that's an invalid configuration 348 | Log.d(MP_TAG, "We're trying to use a ServiceBackedMediaPlayer but we shouldn't be"); 349 | return true; 350 | } 351 | } 352 | 353 | public boolean canFallback() { 354 | return this.mpi == null || !(this.mpi instanceof AndroidAudioPlayer); 355 | } 356 | 357 | public void fallback() { 358 | smp = null; 359 | setupMpi(this.mpi.mContext); 360 | } 361 | 362 | protected void checkMpi() { 363 | if (this.invalidServiceConnectionConfiguration()) { 364 | this.setupMpi(this.mpi.mContext); 365 | } 366 | } 367 | 368 | private void setupMpi(final Context context) { 369 | lock.lock(); 370 | try { 371 | Log.d(MP_TAG, "setupMpi"); 372 | // Check if the client wants to use the service at all, 373 | // then if we're already using the right kind of media player 374 | if (useSonic() && this.smp != null) { 375 | if (mpi != null && mpi instanceof SonicAudioPlayer) { 376 | Log.d(MP_TAG, "Already using SonicMediaPlayer"); 377 | return; 378 | } else { 379 | Log.d(MP_TAG, "Switching to SonicMediaPlayer"); 380 | switchMediaPlayerImpl(mpi, smp); 381 | return; 382 | } 383 | } else if (this.useService && isPrestoLibraryInstalled()) { 384 | if (mpi != null && mpi instanceof ServiceBackedAudioPlayer) { 385 | Log.d(MP_TAG, "Already using ServiceBackedMediaPlayer"); 386 | return; 387 | } 388 | if (this.sbmp == null) { 389 | Log.d(MP_TAG, "Instantiating new ServiceBackedMediaPlayer"); 390 | this.sbmp = new ServiceBackedAudioPlayer(this, context, 391 | new ServiceConnection() { 392 | public void onServiceConnected(ComponentName className, final IBinder service) { 393 | Thread t = new Thread(new Runnable() { 394 | @Override 395 | public void run() { 396 | // This lock probably isn't granular 397 | // enough 398 | MediaPlayer.this.lock.lock(); 399 | Log.d(MP_TAG, "onServiceConnected"); 400 | try { 401 | switchMediaPlayerImpl(mpi, sbmp); 402 | Log.d(MP_TAG, "End onServiceConnected"); 403 | } finally { 404 | MediaPlayer.this.lock.unlock(); 405 | } 406 | } 407 | }); 408 | t.start(); 409 | } 410 | 411 | public void onServiceDisconnected(ComponentName className) { 412 | MediaPlayer.this.lock.lock(); 413 | try { 414 | // Can't get any more useful information 415 | // out of sbmp 416 | if (MediaPlayer.this.sbmp != null) { 417 | MediaPlayer.this.sbmp.release(); 418 | } 419 | // Unlike most other cases, sbmp gets set 420 | // to null since there's nothing useful 421 | // backing it now 422 | MediaPlayer.this.sbmp = null; 423 | 424 | if (mServiceDisconnectedHandler == null) { 425 | mServiceDisconnectedHandler = new Handler(new Handler.Callback() { 426 | @Override 427 | public boolean handleMessage(Message msg) { 428 | // switchMediaPlayerImpl won't try to 429 | // clone anything from null 430 | lock.lock(); 431 | try { 432 | if (MediaPlayer.this.amp == null) { 433 | // This should never be in this state 434 | MediaPlayer.this.amp = new AndroidAudioPlayer( 435 | MediaPlayer.this, 436 | MediaPlayer.this.mContext, userAgent); 437 | } 438 | // Use sbmp instead of null in case by some miracle it's 439 | // been restored in the meantime 440 | switchMediaPlayerImpl(mpi, amp); 441 | return true; 442 | } finally { 443 | lock.unlock(); 444 | } 445 | } 446 | }); 447 | } 448 | 449 | // This code needs to execute on the 450 | // original thread to instantiate 451 | // the new object in the right place 452 | mServiceDisconnectedHandler.sendMessage( 453 | mServiceDisconnectedHandler.obtainMessage()); 454 | // Note that we do NOT want to set 455 | // useService. useService is about 456 | // what the user wants, not what they 457 | // get 458 | } finally { 459 | MediaPlayer.this.lock.unlock(); 460 | } 461 | } 462 | }, userAgent); 463 | } 464 | Log.d(MP_TAG, "Switching to ServiceBackedMediaPlayer"); 465 | switchMediaPlayerImpl(mpi, sbmp); 466 | } else { 467 | if (this.mpi != null && this.mpi instanceof AndroidAudioPlayer) { 468 | Log.d(MP_TAG, "Already using AndroidMediaPlayer"); 469 | return; 470 | } 471 | if (this.amp == null) { 472 | Log.d(MP_TAG, "Instantiating new AndroidMediaPlayer (this should be impossible)"); 473 | this.amp = new AndroidAudioPlayer(this, context, userAgent); 474 | } 475 | switchMediaPlayerImpl(mpi, this.amp); 476 | } 477 | } finally { 478 | lock.unlock(); 479 | } 480 | } 481 | 482 | private void switchMediaPlayerImpl(AbstractAudioPlayer from, AbstractAudioPlayer to) { 483 | Log.d(TAG, "switchMediaPlayerImpl() called with: " + "from = [" + from + "], to = [" + to + "]"); 484 | lock.lock(); 485 | try { 486 | Log.d(MP_TAG, "switchMediaPlayerImpl"); 487 | if (from == to 488 | // Same object, nothing to synchronize 489 | || to == null 490 | // Nothing to copy to (maybe this should throw an error?) 491 | || (to instanceof ServiceBackedAudioPlayer && !((ServiceBackedAudioPlayer) to).isConnected()) 492 | // ServiceBackedMediaPlayer hasn't yet connected, onServiceConnected will take care of the transition 493 | || (MediaPlayer.this.state == State.END)) { 494 | // SonicAudioPlayerState.END is after a release(), no further functions should 495 | // be called on this class and from is likely to have problems 496 | // retrieving state that won't be used anyway 497 | return; 498 | } 499 | // Extract all that we can from the existing implementation 500 | // and copy it to the new implementation 501 | 502 | Log.d(MP_TAG, "switchMediaPlayerImpl(), current state is " + this.state.toString()); 503 | 504 | to.reset(); 505 | 506 | // Do this first so we don't have to prepare the same 507 | // data file twice 508 | to.setEnableSpeedAdjustment(MediaPlayer.this.enableSpeedAdjustment); 509 | 510 | // This is a reasonable place to set all of these, 511 | // none of them require prepare() or the like first 512 | to.setAudioStreamType(this.mAudioStreamType); 513 | to.setLooping(this.mIsLooping); 514 | Log.d(MP_TAG, "Setting playback speed to " + this.mSpeedMultiplier); 515 | to.setVolume(MediaPlayer.this.mLeftVolume, MediaPlayer.this.mRightVolume); 516 | to.setWakeMode(this.mContext, this.mWakeMode); 517 | 518 | Log.d(MP_TAG, "asserting at least one data source is null"); 519 | assert ((MediaPlayer.this.stringDataSource == null) || (MediaPlayer.this.uriDataSource == null)); 520 | 521 | if (uriDataSource != null) { 522 | Log.d(MP_TAG, "switchMediaPlayerImpl(): uriDataSource != null"); 523 | try { 524 | to.setDataSource(this.mContext, uriDataSource); 525 | } catch (IOException e) { 526 | e.printStackTrace(); 527 | } 528 | } 529 | if (stringDataSource != null) { 530 | Log.d(MP_TAG, "switchMediaPlayerImpl(): stringDataSource != null"); 531 | try { 532 | to.setDataSource(stringDataSource); 533 | } catch (Exception e) { 534 | e.printStackTrace(); 535 | } 536 | } 537 | 538 | // (native) mediaplayer has to be initialized 539 | to.setPitchStepsAdjustment(this.mPitchStepsAdjustment); 540 | to.setPlaybackSpeed(this.mSpeedMultiplier); 541 | 542 | if ((this.state == State.PREPARED) 543 | || (this.state == State.PREPARING) 544 | || (this.state == State.PAUSED) 545 | || (this.state == State.STOPPED) 546 | || (this.state == State.STARTED) 547 | || (this.state == State.PLAYBACK_COMPLETED)) { 548 | Log.d(MP_TAG, "switchMediaPlayerImpl(): prepare and seek"); 549 | // Use prepare here instead of prepareAsync so that 550 | // we wait for it to be ready before we try to use it 551 | try { 552 | to.muteNextOnPrepare(); 553 | to.prepare(); 554 | } catch (Exception e) { 555 | e.printStackTrace(); 556 | } 557 | 558 | int seekPos = 0; 559 | if (from != null) { 560 | seekPos = from.getCurrentPosition(); 561 | } else if (this.lastKnownPosition < to.getDuration()) { 562 | // This can happen if the Service unexpectedly 563 | // disconnected. Because it would result in too much 564 | // information being passed around, we don't constantly 565 | // poll for the lastKnownPosition, but we'll save it 566 | // when getCurrentPosition is called 567 | seekPos = this.lastKnownPosition; 568 | } 569 | if (seekPos > 0) { 570 | to.muteNextSeek(); 571 | to.seekTo(seekPos); 572 | } 573 | } 574 | if (from != null && from.isPlaying()) { 575 | from.pause(); 576 | } 577 | if (this.state == State.STARTED || this.state == State.PAUSED || 578 | this.state == State.STOPPED) { 579 | Log.d(MP_TAG, "switchMediaPlayerImpl(): start"); 580 | to.start(); 581 | } 582 | 583 | if (this.state == State.PAUSED) { 584 | Log.d(MP_TAG, "switchMediaPlayerImpl(): paused"); 585 | to.pause(); 586 | } else if (this.state == State.STOPPED) { 587 | Log.d(MP_TAG, "switchMediaPlayerImpl(): stopped"); 588 | to.stop(); 589 | } 590 | 591 | this.mpi = to; 592 | Log.d(TAG, "Switched to " + to.getClass().toString()); 593 | 594 | // Cheating here by relying on the side effect in 595 | // on(Pitch|Speed)AdjustmentAvailableChanged 596 | if ((to.canSetPitch() != this.pitchAdjustmentAvailable) 597 | && (this.onPitchAdjustmentAvailableChangedListener != null)) { 598 | this.onPitchAdjustmentAvailableChangedListener.onPitchAdjustmentAvailableChanged( 599 | this, to.canSetPitch()); 600 | } 601 | if ((to.canSetSpeed() != this.speedAdjustmentAvailable) 602 | && (this.onSpeedAdjustmentAvailableChangedListener != null)) { 603 | this.onSpeedAdjustmentAvailableChangedListener.onSpeedAdjustmentAvailableChanged( 604 | this, to.canSetSpeed()); 605 | } 606 | Log.d(MP_TAG, "switchMediaPlayerImpl() " + this.state.toString()); 607 | } finally { 608 | lock.unlock(); 609 | } 610 | } 611 | 612 | /** 613 | * Returns true if pitch can be changed at this moment 614 | * 615 | * @return True if pitch can be changed 616 | */ 617 | public boolean canSetPitch() { 618 | lock.lock(); 619 | try { 620 | return this.mpi.canSetPitch(); 621 | } finally { 622 | lock.unlock(); 623 | } 624 | } 625 | 626 | /** 627 | * Returns true if speed can be changed at this moment 628 | * 629 | * @return True if speed can be changed 630 | */ 631 | public boolean canSetSpeed() { 632 | lock.lock(); 633 | try { 634 | return this.mpi.canSetSpeed(); 635 | } finally { 636 | lock.unlock(); 637 | } 638 | } 639 | 640 | public boolean canDownmix() { 641 | lock.lock(); 642 | try { 643 | return this.mpi.canDownmix(); 644 | } finally { 645 | lock.unlock(); 646 | } 647 | } 648 | 649 | protected void finalize() throws Throwable { 650 | lock.lock(); 651 | try { 652 | Log.d(MP_TAG, "finalize()"); 653 | this.release(); 654 | } finally { 655 | lock.unlock(); 656 | } 657 | } 658 | 659 | /** 660 | * Returns the number of steps (in a musical scale) by which playback is 661 | * currently shifted. When greater than zero, pitch is shifted up. When less 662 | * than zero, pitch is shifted down. 663 | * 664 | * @return The number of steps pitch is currently shifted by 665 | */ 666 | public float getCurrentPitchStepsAdjustment() { 667 | lock.lock(); 668 | try { 669 | return this.mpi.getCurrentPitchStepsAdjustment(); 670 | } finally { 671 | lock.unlock(); 672 | } 673 | } 674 | 675 | /** 676 | * Functions identically to android.media.MediaPlayer.getCurrentPosition() 677 | * Accurate only to frame size of encoded data (26 ms for MP3s) 678 | * 679 | * @return Current position (in milliseconds) 680 | */ 681 | public int getCurrentPosition() { 682 | lock.lock(); 683 | try { 684 | return (this.lastKnownPosition = this.mpi.getCurrentPosition()); 685 | } finally { 686 | lock.unlock(); 687 | } 688 | } 689 | 690 | /** 691 | * Returns the current speed multiplier. Defaults to 1.0 (normal speed) 692 | * 693 | * @return The current speed multiplier 694 | */ 695 | public float getCurrentSpeedMultiplier() { 696 | lock.lock(); 697 | try { 698 | return this.mpi.getCurrentSpeedMultiplier(); 699 | } finally { 700 | lock.unlock(); 701 | } 702 | } 703 | 704 | /** 705 | * Functions identically to android.media.MediaPlayer.getDuration() 706 | * 707 | * @return Length of the track (in milliseconds) 708 | */ 709 | public int getDuration() { 710 | lock.lock(); 711 | try { 712 | return this.mpi.getDuration(); 713 | } finally { 714 | lock.unlock(); 715 | } 716 | } 717 | 718 | /** 719 | * Get the maximum value that can be passed to setPlaybackSpeed 720 | * 721 | * @return The maximum speed multiplier 722 | */ 723 | public float getMaxSpeedMultiplier() { 724 | lock.lock(); 725 | try { 726 | return this.mpi.getMaxSpeedMultiplier(); 727 | } finally { 728 | lock.unlock(); 729 | } 730 | } 731 | 732 | /** 733 | * Get the minimum value that can be passed to setPlaybackSpeed 734 | * 735 | * @return The minimum speed multiplier 736 | */ 737 | public float getMinSpeedMultiplier() { 738 | lock.lock(); 739 | try { 740 | return this.mpi.getMinSpeedMultiplier(); 741 | } finally { 742 | lock.unlock(); 743 | } 744 | } 745 | 746 | /** 747 | * Gets the version code of the backing service 748 | * 749 | * @return -1 if ServiceBackedMediaPlayer is not used, 0 if the service is not 750 | * connected, otherwise the version code retrieved from the service 751 | */ 752 | public int getServiceVersionCode() { 753 | lock.lock(); 754 | try { 755 | if (this.mpi instanceof ServiceBackedAudioPlayer) { 756 | return ((ServiceBackedAudioPlayer) this.mpi).getServiceVersionCode(); 757 | } else { 758 | return -1; 759 | } 760 | } finally { 761 | lock.unlock(); 762 | } 763 | } 764 | 765 | /** 766 | * Gets the version name of the backing service 767 | * 768 | * @return null if ServiceBackedMediaPlayer is not used, empty string if 769 | * the service is not connected, otherwise the version name retrieved from 770 | * the service 771 | */ 772 | public String getServiceVersionName() { 773 | lock.lock(); 774 | try { 775 | if (this.mpi instanceof ServiceBackedAudioPlayer) { 776 | return ((ServiceBackedAudioPlayer) this.mpi).getServiceVersionName(); 777 | } else { 778 | return null; 779 | } 780 | } finally { 781 | lock.unlock(); 782 | } 783 | } 784 | 785 | /** 786 | * Functions identically to android.media.MediaPlayer.isLooping() 787 | * 788 | * @return True if the track is looping 789 | */ 790 | public boolean isLooping() { 791 | lock.lock(); 792 | try { 793 | return this.mpi.isLooping(); 794 | } finally { 795 | lock.unlock(); 796 | } 797 | } 798 | 799 | /** 800 | * Functions identically to android.media.MediaPlayer.isPlaying() 801 | * 802 | * @return True if the track is playing 803 | */ 804 | public boolean isPlaying() { 805 | lock.lock(); 806 | try { 807 | return this.mpi.isPlaying(); 808 | } finally { 809 | lock.unlock(); 810 | } 811 | } 812 | 813 | /** 814 | * Returns true if this MediaPlayer has access to the Presto 815 | * library 816 | * 817 | * @return True if the Presto library is installed 818 | */ 819 | public boolean isPrestoLibraryInstalled() { 820 | if (this.mpi == null || this.mpi.mContext == null) { 821 | return false; 822 | } 823 | return isPrestoLibraryInstalled(this.mpi.mContext); 824 | } 825 | 826 | /** 827 | * Open the Android Market page in the same context as this MediaPlayer 828 | */ 829 | public void openPrestoMarketIntent() { 830 | if ((this.mpi != null) && (this.mpi.mContext != null)) { 831 | openPrestoMarketIntent(this.mpi.mContext); 832 | } 833 | } 834 | 835 | /** 836 | * Functions identically to android.media.MediaPlayer.pause() Pauses the 837 | * track 838 | */ 839 | public void pause() { 840 | lock.lock(); 841 | try { 842 | checkMpi(); 843 | this.state = State.PAUSED; 844 | this.mpi.pause(); 845 | } finally { 846 | lock.unlock(); 847 | } 848 | } 849 | 850 | /** 851 | * Functions identically to android.media.MediaPlayer.prepare() Prepares the 852 | * track. This or prepareAsync must be called before start() 853 | */ 854 | public void prepare() throws IllegalStateException, IOException { 855 | lock.lock(); 856 | try { 857 | Log.d(MP_TAG, "prepare() using " + ((this.mpi == null) ? "null (this shouldn't happen)" : this.mpi.getClass().toString()) + " state " + this.state.toString()); 858 | Log.d(MP_TAG, "onPreparedListener is: " + ((this.onPreparedListener == null) ? "null" : "non-null")); 859 | Log.d(MP_TAG, "preparedListener is: " + ((this.preparedListener == null) ? "null" : "non-null")); 860 | checkMpi(); 861 | this.mpi.prepare(); 862 | this.state = State.PREPARED; 863 | Log.d(MP_TAG, "prepare() finished"); 864 | } finally { 865 | lock.unlock(); 866 | } 867 | } 868 | 869 | /** 870 | * Functions identically to android.media.MediaPlayer.prepareAsync() 871 | * Prepares the track. This or prepare must be called before start() 872 | */ 873 | public void prepareAsync() { 874 | lock.lock(); 875 | try { 876 | Log.d(MP_TAG, "prepareAsync()"); 877 | checkMpi(); 878 | this.state = State.PREPARING; 879 | this.mpi.prepareAsync(); 880 | } finally { 881 | lock.unlock(); 882 | } 883 | } 884 | 885 | /** 886 | * Functions identically to android.media.MediaPlayer.release() Releases the 887 | * underlying resources used by the media player. 888 | */ 889 | public void release() { 890 | lock.lock(); 891 | try { 892 | Log.d(MP_TAG, "Releasing MediaPlayer"); 893 | 894 | this.state = State.END; 895 | if (this.amp != null) { 896 | this.amp.release(); 897 | } 898 | if (this.sbmp != null) { 899 | this.sbmp.release(); 900 | } 901 | 902 | this.onBufferingUpdateListener = null; 903 | this.onCompletionListener = null; 904 | this.onErrorListener = null; 905 | this.onInfoListener = null; 906 | this.preparedListener = null; 907 | this.onPitchAdjustmentAvailableChangedListener = null; 908 | this.pitchAdjustmentAvailableChangedListener = null; 909 | Log.d(MP_TAG, "Setting onSeekCompleteListener to null 871"); 910 | this.onSeekCompleteListener = null; 911 | this.onSpeedAdjustmentAvailableChangedListener = null; 912 | this.speedAdjustmentAvailableChangedListener = null; 913 | } finally { 914 | lock.unlock(); 915 | } 916 | } 917 | 918 | /** 919 | * Functions identically to android.media.MediaPlayer.reset() Resets the 920 | * track to idle state 921 | */ 922 | public void reset() { 923 | lock.lock(); 924 | try { 925 | this.state = State.IDLE; 926 | this.stringDataSource = null; 927 | this.uriDataSource = null; 928 | this.mpi.reset(); 929 | } finally { 930 | lock.unlock(); 931 | } 932 | } 933 | 934 | /** 935 | * Functions identically to android.media.MediaPlayer.seekTo(int msec) Seeks 936 | * to msec in the track 937 | */ 938 | public void seekTo(int msec) throws IllegalStateException { 939 | lock.lock(); 940 | try { 941 | this.mpi.seekTo(msec); 942 | } finally { 943 | lock.unlock(); 944 | } 945 | } 946 | 947 | /** 948 | * Functions identically to android.media.MediaPlayer.setAudioStreamType(int 949 | * streamtype) Sets the audio stream type. 950 | */ 951 | public void setAudioStreamType(int streamtype) { 952 | lock.lock(); 953 | try { 954 | this.mAudioStreamType = streamtype; 955 | this.mpi.setAudioStreamType(streamtype); 956 | } finally { 957 | lock.unlock(); 958 | } 959 | } 960 | 961 | /** 962 | * Functions identically to android.media.MediaPlayer.setDataSource(Context 963 | * context, Uri uri) Sets uri as data source in the context given 964 | */ 965 | public void setDataSource(Context context, Uri uri) 966 | throws IllegalArgumentException, IllegalStateException, IOException { 967 | lock.lock(); 968 | try { 969 | Log.d(MP_TAG, "In setDataSource(context, " + uri.toString() + "), using " + this.mpi.getClass().toString()); 970 | checkMpi(); 971 | this.state = State.INITIALIZED; 972 | this.stringDataSource = null; 973 | this.uriDataSource = uri; 974 | this.mpi.setDataSource(context, uri); 975 | } finally { 976 | lock.unlock(); 977 | } 978 | } 979 | 980 | /** 981 | * Functions identically to android.media.MediaPlayer.setDataSource(String 982 | * path) Sets the data source of the track to a file given. 983 | */ 984 | public void setDataSource(String path) throws IllegalArgumentException, 985 | IllegalStateException, IOException { 986 | lock.lock(); 987 | try { 988 | Log.d(MP_TAG, "In setDataSource(context, " + path + ")"); 989 | checkMpi(); 990 | this.state = State.INITIALIZED; 991 | this.stringDataSource = path; 992 | this.uriDataSource = null; 993 | this.mpi.setDataSource(path); 994 | } finally { 995 | lock.unlock(); 996 | } 997 | } 998 | 999 | /** 1000 | * Sets whether to use speed adjustment or not. Speed adjustment on is more 1001 | * computation-intensive than with it off. 1002 | * 1003 | * @param enableSpeedAdjustment Whether speed adjustment should be supported. 1004 | */ 1005 | public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) { 1006 | lock.lock(); 1007 | try { 1008 | this.enableSpeedAdjustment = enableSpeedAdjustment; 1009 | this.mpi.setEnableSpeedAdjustment(enableSpeedAdjustment); 1010 | } finally { 1011 | lock.unlock(); 1012 | } 1013 | } 1014 | 1015 | /** 1016 | * Functions identically to android.media.MediaPlayer.setLooping(boolean 1017 | * loop) Sets the track to loop infinitely if loop is true, play once if 1018 | * loop is false 1019 | */ 1020 | public void setLooping(boolean loop) { 1021 | lock.lock(); 1022 | try { 1023 | this.mIsLooping = loop; 1024 | this.mpi.setLooping(loop); 1025 | } finally { 1026 | lock.unlock(); 1027 | } 1028 | } 1029 | 1030 | /** 1031 | * Sets the number of steps (in a musical scale) by which playback is 1032 | * currently shifted. When greater than zero, pitch is shifted up. When less 1033 | * than zero, pitch is shifted down. 1034 | * 1035 | * @param pitchSteps The number of steps by which to shift playback 1036 | */ 1037 | public void setPitchStepsAdjustment(float pitchSteps) { 1038 | lock.lock(); 1039 | try { 1040 | this.mPitchStepsAdjustment = pitchSteps; 1041 | this.mpi.setPitchStepsAdjustment(pitchSteps); 1042 | } finally { 1043 | lock.unlock(); 1044 | } 1045 | } 1046 | 1047 | private static float getPitchStepsAdjustment(float pitch) { 1048 | return (float) (Math.log(pitch) / (2 * Math.log(PITCH_STEP_CONSTANT))); 1049 | } 1050 | 1051 | /** 1052 | * Sets the percentage by which pitch is currently shifted. When greater 1053 | * than zero, pitch is shifted up. When less than zero, pitch is shifted 1054 | * down 1055 | * 1056 | * @param pitch The percentage to shift pitch 1057 | */ 1058 | public void setPlaybackPitch(float pitch) { 1059 | lock.lock(); 1060 | try { 1061 | this.mPitchStepsAdjustment = getPitchStepsAdjustment(pitch); 1062 | this.mpi.setPlaybackPitch(pitch); 1063 | } finally { 1064 | lock.unlock(); 1065 | } 1066 | } 1067 | 1068 | /** 1069 | * Set playback speed. 1.0 is normal speed, 2.0 is double speed, and so on. 1070 | * Speed should never be set to 0 or below. 1071 | * 1072 | * @param f The speed multiplier to use for further playback 1073 | */ 1074 | public void setPlaybackSpeed(float f) { 1075 | lock.lock(); 1076 | try { 1077 | this.mSpeedMultiplier = f; 1078 | this.mpi.setPlaybackSpeed(f); 1079 | } finally { 1080 | lock.unlock(); 1081 | } 1082 | } 1083 | 1084 | public void setDownmix(boolean enable) { 1085 | lock.lock(); 1086 | try { 1087 | this.mpi.setDownmix(enable); 1088 | } finally { 1089 | lock.unlock(); 1090 | } 1091 | } 1092 | 1093 | /** 1094 | * Functions identically to android.media.MediaPlayer.setVolume(float 1095 | * leftVolume, float rightVolume) Sets the stereo volume 1096 | */ 1097 | public void setVolume(float leftVolume, float rightVolume) { 1098 | lock.lock(); 1099 | try { 1100 | this.mLeftVolume = leftVolume; 1101 | this.mRightVolume = rightVolume; 1102 | this.mpi.setVolume(leftVolume, rightVolume); 1103 | } finally { 1104 | lock.unlock(); 1105 | } 1106 | } 1107 | 1108 | /** 1109 | * Functions identically to android.media.MediaPlayer.setWakeMode(Context 1110 | * context, int mode) Acquires a wake lock in the context given. You must 1111 | * request the appropriate permissions in your AndroidManifest.xml file. 1112 | */ 1113 | public void setWakeMode(Context context, int mode) { 1114 | lock.lock(); 1115 | try { 1116 | this.mWakeMode = mode; 1117 | this.mpi.setWakeMode(context, mode); 1118 | } finally { 1119 | lock.unlock(); 1120 | } 1121 | } 1122 | 1123 | /** 1124 | * Functions identically to 1125 | * android.media.MediaPlayer.setOnCompletionListener(OnCompletionListener 1126 | * listener) Sets a listener to be used when a track completes playing. 1127 | */ 1128 | public void setOnBufferingUpdateListener(OnBufferingUpdateListener listener) { 1129 | lock.lock(); 1130 | try { 1131 | this.onBufferingUpdateListener = listener; 1132 | } finally { 1133 | lock.unlock(); 1134 | } 1135 | } 1136 | 1137 | /** 1138 | * Functions identically to 1139 | * android.media.MediaPlayer.setOnCompletionListener(OnCompletionListener 1140 | * listener) Sets a listener to be used when a track completes playing. 1141 | */ 1142 | public void setOnCompletionListener(OnCompletionListener listener) { 1143 | lock.lock(); 1144 | try { 1145 | this.onCompletionListener = listener; 1146 | } finally { 1147 | lock.unlock(); 1148 | } 1149 | } 1150 | 1151 | /** 1152 | * Functions identically to 1153 | * android.media.MediaPlayer.setOnErrorListener(OnErrorListener listener) 1154 | * Sets a listener to be used when a track encounters an error. 1155 | */ 1156 | public void setOnErrorListener(OnErrorListener listener) { 1157 | lock.lock(); 1158 | try { 1159 | this.onErrorListener = listener; 1160 | } finally { 1161 | lock.unlock(); 1162 | } 1163 | } 1164 | 1165 | /** 1166 | * Functions identically to 1167 | * android.media.MediaPlayer.setOnInfoListener(OnInfoListener listener) Sets 1168 | * a listener to be used when a track has info. 1169 | */ 1170 | public void setOnInfoListener(OnInfoListener listener) { 1171 | lock.lock(); 1172 | try { 1173 | this.onInfoListener = listener; 1174 | } finally { 1175 | lock.unlock(); 1176 | } 1177 | } 1178 | 1179 | /** 1180 | * Sets a listener that will fire when pitch adjustment becomes available or 1181 | * stops being available 1182 | */ 1183 | public void setOnPitchAdjustmentAvailableChangedListener( 1184 | OnPitchAdjustmentAvailableChangedListener listener) { 1185 | lock.lock(); 1186 | try { 1187 | this.pitchAdjustmentAvailableChangedListener = listener; 1188 | } finally { 1189 | lock.unlock(); 1190 | } 1191 | } 1192 | 1193 | /** 1194 | * Functions identically to 1195 | * android.media.MediaPlayer.setOnPreparedListener(OnPreparedListener 1196 | * listener) Sets a listener to be used when a track finishes preparing. 1197 | */ 1198 | public void setOnPreparedListener(OnPreparedListener listener) { 1199 | lock.lock(); 1200 | Log.d(MP_TAG, " ++++++++++++++++++++++++++++++++++++++++++++ setOnPreparedListener"); 1201 | try { 1202 | this.preparedListener = listener; 1203 | // For this one, we do not explicitly set the MediaPlayer or the 1204 | // Service listener. This is because in addition to calling the 1205 | // listener provided by the client, it's necessary to change 1206 | // state to PREPARED. See prepareAsync for implementation details 1207 | } finally { 1208 | lock.unlock(); 1209 | } 1210 | } 1211 | 1212 | /** 1213 | * Functions identically to 1214 | * android.media.MediaPlayer.setOnSeekCompleteListener 1215 | * (OnSeekCompleteListener listener) Sets a listener to be used when a track 1216 | * finishes seeking. 1217 | */ 1218 | public void setOnSeekCompleteListener(OnSeekCompleteListener listener) { 1219 | lock.lock(); 1220 | try { 1221 | this.onSeekCompleteListener = listener; 1222 | } finally { 1223 | lock.unlock(); 1224 | } 1225 | } 1226 | 1227 | /** 1228 | * Sets a listener that will fire when speed adjustment becomes available or 1229 | * stops being available 1230 | */ 1231 | public void setOnSpeedAdjustmentAvailableChangedListener( 1232 | OnSpeedAdjustmentAvailableChangedListener listener) { 1233 | lock.lock(); 1234 | try { 1235 | this.speedAdjustmentAvailableChangedListener = listener; 1236 | } finally { 1237 | lock.unlock(); 1238 | } 1239 | } 1240 | 1241 | /** 1242 | * Functions identically to android.media.MediaPlayer.start() Starts a track 1243 | * playing 1244 | */ 1245 | public void start() { 1246 | lock.lock(); 1247 | try { 1248 | Log.d(MP_TAG, "start()"); 1249 | checkMpi(); 1250 | this.state = State.STARTED; 1251 | this.mpi.start(); 1252 | } finally { 1253 | lock.unlock(); 1254 | } 1255 | } 1256 | 1257 | /** 1258 | * Functions identically to android.media.MediaPlayer.stop() Stops a track 1259 | * playing and resets its position to the start. 1260 | */ 1261 | public void stop() { 1262 | lock.lock(); 1263 | try { 1264 | checkMpi(); 1265 | this.state = State.STOPPED; 1266 | this.mpi.stop(); 1267 | } finally { 1268 | lock.unlock(); 1269 | } 1270 | } 1271 | 1272 | } 1273 | -------------------------------------------------------------------------------- /library/src/main/java/org/antennapod/audio/SonicAudioPlayer.java: -------------------------------------------------------------------------------- 1 | //Copyright 2012 James Falcon 2 | // 3 | //Licensed under the Apache License, Version 2.0 (the "License"); 4 | //you may not use this file except in compliance with the License. 5 | //You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | //Unless required by applicable law or agreed to in writing, software 10 | //distributed under the License is distributed on an "AS IS" BASIS, 11 | //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | /* Minor modifications by Martin Fietz 16 | * The original source can be found here: 17 | * https://github.com/TheRealFalcon/Prestissimo/blob/master/src/com/falconware/prestissimo/Track.java 18 | */ 19 | 20 | package org.antennapod.audio; 21 | 22 | import android.annotation.TargetApi; 23 | import android.content.Context; 24 | import android.media.AudioFormat; 25 | import android.media.AudioManager; 26 | import android.media.AudioTrack; 27 | import android.media.MediaCodec; 28 | import android.media.MediaExtractor; 29 | import android.media.MediaFormat; 30 | import android.net.Uri; 31 | import android.os.Build; 32 | import android.os.PowerManager; 33 | import android.util.Log; 34 | 35 | import org.vinuxproject.sonic.Sonic; 36 | 37 | import java.io.IOException; 38 | import java.nio.ByteBuffer; 39 | import java.util.concurrent.atomic.AtomicInteger; 40 | import java.util.concurrent.locks.ReentrantLock; 41 | 42 | import static org.antennapod.audio.SonicAudioPlayerState.END; 43 | import static org.antennapod.audio.SonicAudioPlayerState.ERROR; 44 | import static org.antennapod.audio.SonicAudioPlayerState.IDLE; 45 | import static org.antennapod.audio.SonicAudioPlayerState.INITIALIZED; 46 | import static org.antennapod.audio.SonicAudioPlayerState.PAUSED; 47 | import static org.antennapod.audio.SonicAudioPlayerState.PLAYBACK_COMPLETED; 48 | import static org.antennapod.audio.SonicAudioPlayerState.PREPARED; 49 | import static org.antennapod.audio.SonicAudioPlayerState.PREPARING; 50 | import static org.antennapod.audio.SonicAudioPlayerState.STARTED; 51 | import static org.antennapod.audio.SonicAudioPlayerState.STOPPED; 52 | 53 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 54 | public class SonicAudioPlayer extends AbstractAudioPlayer { 55 | 56 | private static final String TAG = SonicAudioPlayer.class.getSimpleName(); 57 | private final static String TAG_TRACK = "SonicTrack"; 58 | 59 | private AudioTrack mTrack; 60 | private int mBufferSize; 61 | private Sonic mSonic; 62 | private MediaExtractor mExtractor; 63 | private MediaCodec mCodec; 64 | private Thread mDecoderThread; 65 | private String mPath; 66 | private Uri mUri; 67 | private final ReentrantLock mLock; 68 | private final Object mDecoderLock; 69 | private boolean mContinue; 70 | private AtomicInteger mInitiatingCount = new AtomicInteger(0); 71 | private AtomicInteger mSeekingCount = new AtomicInteger(0); 72 | private boolean mIsDecoding; 73 | private long mDuration; 74 | private float mCurrentSpeed; 75 | private float mCurrentPitch; 76 | 77 | private final SonicAudioPlayerState state = new SonicAudioPlayerState(); 78 | 79 | private final Context mContext; 80 | private PowerManager.WakeLock mWakeLock = null; 81 | 82 | private boolean mDownMix; 83 | 84 | 85 | SonicAudioPlayer(MediaPlayer owningMediaPlayer, Context context, String userAgent) { 86 | super(owningMediaPlayer, context, userAgent); 87 | mCurrentSpeed = 1.0f; 88 | mCurrentPitch = 1.0f; 89 | mContinue = false; 90 | mIsDecoding = false; 91 | mContext = context; 92 | mPath = null; 93 | mUri = null; 94 | mLock = new ReentrantLock(); 95 | mDecoderLock = new Object(); 96 | mDownMix = false; 97 | } 98 | 99 | @Override 100 | public int getAudioSessionId() { 101 | if (mTrack == null) { 102 | return 0; 103 | } 104 | return mTrack.getAudioSessionId(); 105 | } 106 | 107 | @Override 108 | public boolean canSetPitch() { 109 | return true; 110 | } 111 | 112 | @Override 113 | public boolean canSetSpeed() { 114 | return true; 115 | } 116 | 117 | @Override 118 | public float getCurrentPitchStepsAdjustment() { 119 | return mCurrentPitch; 120 | } 121 | 122 | public int getCurrentPosition() { 123 | if (state.is(INITIALIZED) || state.is(IDLE) || state.is(ERROR)) { 124 | return 0; 125 | } 126 | return (int) (mExtractor.getSampleTime() / 1000); 127 | } 128 | 129 | @Override 130 | public float getCurrentSpeedMultiplier() { 131 | return mCurrentSpeed; 132 | } 133 | 134 | @Override 135 | public boolean canDownmix() { 136 | return true; 137 | } 138 | 139 | @Override 140 | public void setDownmix(boolean enable) { 141 | mDownMix = enable; 142 | } 143 | 144 | public int getDuration() { 145 | if (state.is(INITIALIZED) || state.is(IDLE) || state.is(ERROR)) { 146 | error(); 147 | return 0; 148 | } 149 | return (int) (mDuration / 1000); 150 | } 151 | 152 | @Override 153 | public float getMaxSpeedMultiplier() { 154 | return 4.0f; 155 | } 156 | 157 | @Override 158 | public float getMinSpeedMultiplier() { 159 | return 0.5f; 160 | } 161 | 162 | @Override 163 | public boolean isLooping() { 164 | return false; 165 | } 166 | 167 | public boolean isPlaying() { 168 | if (state.is(ERROR)) { 169 | error(); 170 | return false; 171 | } 172 | return state.is(STARTED); 173 | } 174 | 175 | public void pause() { 176 | Log.d(TAG, "pause(), current state: " + state); 177 | if (state.is(PREPARED)) { 178 | Log.d(TAG_TRACK, "PREPARED, ignore pause()"); 179 | return; 180 | } 181 | if (!state.is(STARTED) && !state.is(PAUSED)) { 182 | error(); 183 | return; 184 | } 185 | mTrack.pause(); 186 | state.changeTo(PAUSED); 187 | } 188 | 189 | public void prepare() { 190 | Log.d(TAG, "prepare(), current state: " + state); 191 | if (!state.is(INITIALIZED) && !state.is(STOPPED)) { 192 | error(); 193 | return; 194 | } 195 | doPrepare(); 196 | } 197 | 198 | public void prepareAsync() { 199 | Log.d(TAG, "prepareAsync(), current state: " + state); 200 | if (!state.is(INITIALIZED) && !state.is(STOPPED)) { 201 | error(); 202 | return; 203 | } 204 | 205 | Thread t = new Thread(new Runnable() { 206 | @Override 207 | public void run() { 208 | doPrepare(); 209 | } 210 | }); 211 | t.setDaemon(true); 212 | t.start(); 213 | } 214 | 215 | private void doPrepare() { 216 | boolean streamInitialized; 217 | String lastPath = currentPath(); 218 | 219 | state.changeTo(PREPARING); 220 | try { 221 | streamInitialized = initStream(); 222 | } catch (IOException e) { 223 | String currentPath = currentPath(); 224 | if (currentPath == null || currentPath.equals(lastPath)) { 225 | Log.e(TAG_TRACK, "Failed setting data source!", e); 226 | error(); 227 | } 228 | return; 229 | } 230 | if (streamInitialized) { 231 | if (!state.is(ERROR)) { 232 | state.changeTo(PREPARED); 233 | } 234 | owningMediaPlayer.onPreparedListener.onPrepared(owningMediaPlayer); 235 | } 236 | } 237 | 238 | public void stop() { 239 | if (!state.stoppingAllowed()) { 240 | error(); 241 | Log.d(TAG_TRACK, "Stopping in current state " + state + " not allowed"); 242 | return; 243 | } 244 | state.changeTo(STOPPED); 245 | mContinue = false; 246 | mTrack.pause(); 247 | mTrack.flush(); 248 | } 249 | 250 | public void start() { 251 | if (state.is(STARTED)) { 252 | return; 253 | } 254 | if (state.is(PLAYBACK_COMPLETED) || state.is(PREPARED)) { 255 | if(state.is(PLAYBACK_COMPLETED)) { 256 | try { 257 | initStream(); 258 | } catch (IOException e) { 259 | Log.e(TAG, "initStream() failed"); 260 | error(); 261 | return; 262 | } 263 | } 264 | state.changeTo(STARTED); 265 | mContinue = true; 266 | mTrack.play(); 267 | decode(); 268 | } else if (state.is(PAUSED)) { 269 | state.changeTo(STARTED); 270 | synchronized (mDecoderLock) { 271 | mDecoderLock.notify(); 272 | } 273 | mTrack.play(); 274 | } else { 275 | state.changeTo(ERROR); 276 | if (mTrack != null) { 277 | error(); 278 | } else { 279 | Log.d("start", "Attempting to start while in idle after construction. " + 280 | "Not allowed by no callbacks called"); 281 | } 282 | } 283 | } 284 | 285 | public void release() { 286 | reset(); 287 | state.changeTo(END); 288 | } 289 | 290 | public void reset() { 291 | mLock.lock(); 292 | mContinue = false; 293 | try { 294 | if (mDecoderThread != null && !state.is(PLAYBACK_COMPLETED)) { 295 | while (mIsDecoding) { 296 | synchronized (mDecoderLock) { 297 | mDecoderLock.notify(); 298 | mDecoderLock.wait(); 299 | } 300 | } 301 | } 302 | } catch (InterruptedException e) { 303 | Log.e(TAG_TRACK, "Interrupted in reset while waiting for decoder thread to stop.", e); 304 | } 305 | if (mCodec != null) { 306 | mCodec.release(); 307 | mCodec = null; 308 | } 309 | if (mExtractor != null) { 310 | mExtractor.release(); 311 | mExtractor = null; 312 | } 313 | if (mTrack != null) { 314 | mTrack.release(); 315 | mTrack = null; 316 | } 317 | mPath = null; 318 | mUri = null; 319 | mBufferSize = 0; 320 | state.changeTo(IDLE); 321 | mLock.unlock(); 322 | } 323 | 324 | public void seekTo(final int msec) { 325 | boolean playing = false; 326 | 327 | if (!state.seekingAllowed()) { 328 | error(); 329 | Log.d(TAG_TRACK, "Seeking in current state " + state + " is not seekable"); 330 | return; 331 | } 332 | 333 | if (state.is(STARTED)) { 334 | playing = true; 335 | pause(); 336 | } 337 | if (mTrack == null) { 338 | return; 339 | } 340 | mTrack.flush(); 341 | 342 | final boolean wasPlaying = playing; 343 | 344 | Runnable seekRunnable = new Runnable() { 345 | 346 | @Override 347 | public void run() { 348 | String lastPath = currentPath(); 349 | 350 | mSeekingCount.incrementAndGet(); 351 | try { 352 | mExtractor.seekTo(((long) msec * 1000), MediaExtractor.SEEK_TO_PREVIOUS_SYNC); 353 | } catch (Exception e) { 354 | error(); 355 | return; 356 | } finally { 357 | mSeekingCount.decrementAndGet(); 358 | } 359 | 360 | // make sure that the current episode didn't change while seeking 361 | if (mExtractor != null && lastPath != null && lastPath.equals(currentPath()) && !state.is(ERROR)) { 362 | 363 | Log.d(TAG, "seek completed, position: " + getCurrentPosition()); 364 | 365 | if (owningMediaPlayer.onSeekCompleteListener != null) { 366 | owningMediaPlayer.onSeekCompleteListener.onSeekComplete(owningMediaPlayer); 367 | } 368 | if (wasPlaying) { 369 | start(); 370 | } 371 | } 372 | } 373 | }; 374 | 375 | // when streaming, the seeking is started in another thread to prevent UI locking 376 | if (mUri != null) { 377 | Thread t = new Thread(seekRunnable); 378 | t.setDaemon(true); 379 | t.start(); 380 | } else { 381 | seekRunnable.run(); 382 | } 383 | } 384 | 385 | @Override 386 | public void setAudioStreamType(int streamtype) {} 387 | 388 | @Override 389 | public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) {} 390 | 391 | @Override 392 | public void setLooping(boolean loop) {} 393 | 394 | @Override 395 | public void setPitchStepsAdjustment(float pitchSteps) { 396 | mCurrentPitch += pitchSteps; 397 | } 398 | 399 | @Override 400 | public void setPlaybackPitch(float f) { 401 | mCurrentSpeed = f; 402 | } 403 | 404 | @Override 405 | public void setPlaybackSpeed(float f) { 406 | mCurrentSpeed = f; 407 | } 408 | 409 | @Override 410 | public void setDataSource(String path) { 411 | if (!state.settingDataSourceAllowed()) { 412 | error(); 413 | return; 414 | } 415 | mPath = path; 416 | state.changeTo(INITIALIZED); 417 | } 418 | 419 | @Override 420 | public void setDataSource(Context context, Uri uri) { 421 | if (!state.settingDataSourceAllowed()) { 422 | error(); 423 | return; 424 | } 425 | mUri = uri; 426 | state.changeTo(INITIALIZED); 427 | } 428 | 429 | void setDownMix(boolean downmix) { 430 | mDownMix = downmix; 431 | } 432 | 433 | @SuppressWarnings("deprecation") 434 | @Override 435 | public void setVolume(float leftVolume, float rightVolume) { 436 | // Pass call directly to AudioTrack if available. 437 | if (mTrack == null) { 438 | return; 439 | } 440 | mTrack.setStereoVolume(leftVolume, rightVolume); 441 | } 442 | 443 | @Override 444 | public void setWakeMode(Context context, int mode) { 445 | boolean wasHeld = false; 446 | if (mWakeLock != null) { 447 | if (mWakeLock.isHeld()) { 448 | wasHeld = true; 449 | mWakeLock.release(); 450 | } 451 | mWakeLock = null; 452 | } 453 | 454 | if (mode > 0) { 455 | PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 456 | mWakeLock = pm.newWakeLock(mode, this.getClass().getName()); 457 | mWakeLock.setReferenceCounted(false); 458 | if (wasHeld) { 459 | mWakeLock.acquire(); 460 | } 461 | } 462 | } 463 | 464 | private void error() { 465 | error(0); 466 | } 467 | 468 | private void error(int extra) { 469 | if (state.is(ERROR)) { 470 | return; 471 | } 472 | state.changeTo(ERROR); 473 | if (owningMediaPlayer.onErrorListener != null) { 474 | boolean handled = owningMediaPlayer.onErrorListener.onError(owningMediaPlayer, 0, extra); 475 | if (!handled && owningMediaPlayer.onCompletionListener != null) { 476 | owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer); 477 | } 478 | } 479 | } 480 | 481 | private String currentPath() { 482 | if (mPath != null) { 483 | return mPath; 484 | } else if (mUri != null) { 485 | return mUri.toString(); 486 | } 487 | 488 | return null; 489 | } 490 | 491 | private boolean initStream() throws IOException { 492 | 493 | // Since this method could be running in another thread, when "setDataSource" returns 494 | // we need to check if the media path has changed 495 | String lastPath = currentPath(); 496 | 497 | mInitiatingCount.incrementAndGet(); 498 | try { 499 | mExtractor = new MediaExtractor(); 500 | 501 | if (mPath != null) { 502 | mExtractor.setDataSource(mPath, getHeaders()); 503 | } else if (mUri != null) { 504 | mExtractor.setDataSource(mContext, mUri, getHeaders()); 505 | } else { 506 | throw new IOException("Neither path nor uri set"); 507 | } 508 | } finally { 509 | mInitiatingCount.decrementAndGet(); 510 | } 511 | 512 | String currentPath = currentPath(); 513 | if (currentPath == null || !currentPath.equals(lastPath) || state.is(ERROR)) { 514 | return false; 515 | } 516 | 517 | mLock.lock(); 518 | 519 | if (mExtractor == null) { 520 | mLock.unlock(); 521 | throw new IOException("Extractor is null"); 522 | } 523 | 524 | int trackNum = -1; 525 | for (int i = 0; i < mExtractor.getTrackCount(); i++) { 526 | final MediaFormat oFormat = mExtractor.getTrackFormat(i); 527 | String mime = oFormat.getString(MediaFormat.KEY_MIME); 528 | if (trackNum < 0 && mime.startsWith("audio/")) { 529 | trackNum = i; 530 | } else { 531 | mExtractor.unselectTrack(i); 532 | } 533 | } 534 | 535 | if (trackNum < 0) { 536 | mLock.unlock(); 537 | throw new IOException("No audio track found"); 538 | } 539 | 540 | final MediaFormat oFormat = mExtractor.getTrackFormat(trackNum); 541 | try { 542 | int sampleRate = oFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE); 543 | int channelCount = oFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); 544 | final String mime = oFormat.getString(MediaFormat.KEY_MIME); 545 | mDuration = oFormat.getLong(MediaFormat.KEY_DURATION); 546 | 547 | Log.v(TAG_TRACK, "Sample rate: " + sampleRate); 548 | Log.v(TAG_TRACK, "Channel count: " + channelCount); 549 | Log.v(TAG_TRACK, "Mime type: " + mime); 550 | Log.v(TAG_TRACK, "Duration: " + mDuration); 551 | 552 | initDevice(sampleRate, channelCount); 553 | mExtractor.selectTrack(trackNum); 554 | mCodec = MediaCodec.createDecoderByType(mime); 555 | mCodec.configure(oFormat, null, null, 0); 556 | } catch (Throwable th) { 557 | Log.e(TAG, Log.getStackTraceString(th)); 558 | error(); 559 | } 560 | mLock.unlock(); 561 | 562 | return true; 563 | } 564 | 565 | private void initDevice(int sampleRate, int numChannels) { 566 | mLock.lock(); 567 | final int format = findFormatFromChannels(numChannels); 568 | int oldBufferSize = mBufferSize; 569 | mBufferSize = AudioTrack.getMinBufferSize(sampleRate, format, AudioFormat.ENCODING_PCM_16BIT); 570 | if (mBufferSize != oldBufferSize) { 571 | if (mTrack != null) { 572 | mTrack.release(); 573 | } 574 | mTrack = createAudioTrack(sampleRate, format, mBufferSize); 575 | } 576 | mSonic = new Sonic(sampleRate, numChannels); 577 | mLock.unlock(); 578 | } 579 | 580 | private static int findFormatFromChannels(int numChannels) { 581 | switch (numChannels) { 582 | case 1: 583 | return AudioFormat.CHANNEL_OUT_MONO; 584 | case 2: 585 | return AudioFormat.CHANNEL_OUT_STEREO; 586 | case 3: 587 | return AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER; 588 | case 4: 589 | return AudioFormat.CHANNEL_OUT_QUAD; 590 | case 5: 591 | return AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER; 592 | case 6: 593 | return AudioFormat.CHANNEL_OUT_5POINT1; 594 | case 7: 595 | return AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER; 596 | case 8: 597 | if (Build.VERSION.SDK_INT >= 23) { 598 | return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; 599 | } else { 600 | return -1; 601 | } 602 | default: 603 | return -1; // Error 604 | } 605 | } 606 | 607 | private AudioTrack createAudioTrack(int sampleRate, 608 | int channelConfig, 609 | int minBufferSize) { 610 | for (int i = 4; i >= 1; i--) { 611 | int bufferSize = minBufferSize * i; 612 | 613 | AudioTrack audioTrack = null; 614 | try { 615 | audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, 616 | channelConfig, AudioFormat.ENCODING_PCM_16BIT, bufferSize, 617 | AudioTrack.MODE_STREAM); 618 | if (audioTrack.getState() == AudioTrack.STATE_INITIALIZED) { 619 | mBufferSize = bufferSize; 620 | return audioTrack; 621 | } else { 622 | audioTrack.release(); 623 | } 624 | } catch (IllegalArgumentException e) { 625 | if (audioTrack != null) { 626 | audioTrack.release(); 627 | } 628 | } 629 | } 630 | throw new IllegalStateException("Could not create buffer for AudioTrack"); 631 | } 632 | 633 | @SuppressWarnings("deprecation") 634 | private void decode() { 635 | mDecoderThread = new Thread(new Runnable() { 636 | 637 | private int currHeadPos; 638 | 639 | @Override 640 | public void run() { 641 | 642 | mIsDecoding = true; 643 | mCodec.start(); 644 | 645 | ByteBuffer[] inputBuffers = mCodec.getInputBuffers(); 646 | ByteBuffer[] outputBuffers = mCodec.getOutputBuffers(); 647 | 648 | boolean sawInputEOS = false; 649 | boolean sawOutputEOS = false; 650 | 651 | while (!sawInputEOS && !sawOutputEOS && mContinue) { 652 | currHeadPos = mTrack.getPlaybackHeadPosition(); 653 | if (state.is(PAUSED)) { 654 | System.out.println("Decoder changed to PAUSED"); 655 | try { 656 | synchronized (mDecoderLock) { 657 | mDecoderLock.wait(); 658 | System.out.println("Done with wait"); 659 | } 660 | } catch (InterruptedException e) { 661 | // Purposely not doing anything here 662 | } 663 | continue; 664 | } 665 | 666 | if (null != mSonic) { 667 | mSonic.setSpeed(mCurrentSpeed); 668 | mSonic.setPitch(mCurrentPitch); 669 | } 670 | 671 | int inputBufIndex = mCodec.dequeueInputBuffer(200); 672 | if (inputBufIndex >= 0) { 673 | ByteBuffer dstBuf = inputBuffers[inputBufIndex]; 674 | int sampleSize = mExtractor.readSampleData(dstBuf, 0); 675 | long presentationTimeUs = 0; 676 | if (sampleSize < 0) { 677 | sawInputEOS = true; 678 | sampleSize = 0; 679 | } else { 680 | presentationTimeUs = mExtractor.getSampleTime(); 681 | } 682 | mCodec.queueInputBuffer( 683 | inputBufIndex, 684 | 0, 685 | sampleSize, 686 | presentationTimeUs, 687 | sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 688 | if (!sawInputEOS) { 689 | mExtractor.advance(); 690 | } 691 | } 692 | 693 | final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 694 | byte[] modifiedSamples = new byte[info.size]; 695 | 696 | int res; 697 | do { 698 | res = mCodec.dequeueOutputBuffer(info, 200); 699 | if (res >= 0) { 700 | int outputBufIndex = res; 701 | final byte[] chunk = new byte[info.size]; 702 | outputBuffers[res].get(chunk); 703 | outputBuffers[res].clear(); 704 | 705 | if (chunk.length > 0) { 706 | mSonic.writeBytesToStream(chunk, chunk.length); 707 | } else { 708 | mSonic.flushStream(); 709 | } 710 | int available = mSonic.samplesAvailable(); 711 | if (available > 0) { 712 | if (modifiedSamples.length < available) { 713 | modifiedSamples = new byte[available]; 714 | } 715 | if (mDownMix && mSonic.getNumChannels() == 2) { 716 | int maxBytes = (available / 4) * 4; 717 | mSonic.readBytesFromStream(modifiedSamples, maxBytes); 718 | DownMixer.downMix(modifiedSamples); 719 | mTrack.write(modifiedSamples, 0, maxBytes); 720 | } else { 721 | mSonic.readBytesFromStream(modifiedSamples, available); 722 | mTrack.write(modifiedSamples, 0, available); 723 | } 724 | } 725 | 726 | mCodec.releaseOutputBuffer(outputBufIndex, false); 727 | 728 | if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 729 | sawOutputEOS = true; 730 | } 731 | } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 732 | outputBuffers = mCodec.getOutputBuffers(); 733 | Log.d("PCM", "Output buffers changed"); 734 | } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 735 | final MediaFormat oFormat = mCodec.getOutputFormat(); 736 | Log.d("PCM", "Output format has changed to " + oFormat); 737 | int sampleRate = oFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE); 738 | int channelCount = oFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); 739 | if (sampleRate != mSonic.getSampleRate() || 740 | channelCount != mSonic.getNumChannels()) { 741 | mTrack.stop(); 742 | mLock.lock(); 743 | mTrack.release(); 744 | initDevice(sampleRate, channelCount); 745 | outputBuffers = mCodec.getOutputBuffers(); 746 | mTrack.play(); 747 | mLock.unlock(); 748 | } 749 | } 750 | } while (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED || 751 | res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); 752 | } 753 | Log.d(TAG_TRACK, "Decoding loop exited. Stopping codec and track"); 754 | Log.d(TAG_TRACK, "Duration: " + (int) (mDuration / 1000)); 755 | 756 | if (!((mInitiatingCount.get() > 0) || (mSeekingCount.get() > 0))) { 757 | Log.d(TAG_TRACK, "Current position: " + getCurrentPosition()); 758 | } 759 | mCodec.stop(); 760 | 761 | // wait for track to finish playing 762 | int lastHeadPos; 763 | do { 764 | lastHeadPos = currHeadPos; 765 | try { 766 | Thread.sleep(100); 767 | currHeadPos = mTrack.getPlaybackHeadPosition(); 768 | } catch (InterruptedException e) { /* ignore */ } 769 | } while (currHeadPos != lastHeadPos); 770 | mTrack.stop(); 771 | 772 | Log.d(TAG_TRACK, "Stopped codec and track"); 773 | 774 | if (!((mInitiatingCount.get() > 0) || (mSeekingCount.get() > 0))) { 775 | Log.d(TAG_TRACK, "Current position: " + getCurrentPosition()); 776 | } 777 | mIsDecoding = false; 778 | if (mContinue && (sawInputEOS || sawOutputEOS)) { 779 | state.changeTo(PLAYBACK_COMPLETED); 780 | if (owningMediaPlayer.onCompletionListener != null) { 781 | Thread t = new Thread(new Runnable() { 782 | @Override 783 | public void run() { 784 | owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer); 785 | 786 | } 787 | }); 788 | t.setDaemon(true); 789 | t.start(); 790 | } 791 | } else { 792 | Log.d(TAG_TRACK, "Loop ended before saw input eos or output eos"); 793 | Log.d(TAG_TRACK, "sawInputEOS: " + sawInputEOS); 794 | Log.d(TAG_TRACK, "sawOutputEOS: " + sawOutputEOS); 795 | } 796 | synchronized (mDecoderLock) { 797 | mDecoderLock.notifyAll(); 798 | } 799 | } 800 | }); 801 | mDecoderThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 802 | @Override 803 | public void uncaughtException(Thread thread, Throwable ex) { 804 | Log.e(TAG_TRACK, Log.getStackTraceString(ex)); 805 | error(); 806 | } 807 | }); 808 | mDecoderThread.setDaemon(true); 809 | mDecoderThread.start(); 810 | } 811 | 812 | 813 | } 814 | -------------------------------------------------------------------------------- /library/src/main/java/org/antennapod/audio/SonicAudioPlayerState.java: -------------------------------------------------------------------------------- 1 | package org.antennapod.audio; 2 | 3 | import android.util.Log; 4 | 5 | class SonicAudioPlayerState { 6 | 7 | private static final String TAG = "SonicAudioPlayerState"; 8 | 9 | final static int IDLE = 0; 10 | final static int INITIALIZED = 1; 11 | final static int PREPARING = 2; 12 | final static int PREPARED = 3; 13 | final static int STARTED = 4; 14 | final static int PAUSED = 5; 15 | final static int STOPPED = 6; 16 | final static int PLAYBACK_COMPLETED = 7; 17 | final static int END = 8; 18 | final static int ERROR = 9; 19 | 20 | private int currentState; 21 | 22 | SonicAudioPlayerState() { 23 | currentState = IDLE; 24 | } 25 | 26 | void changeTo(int state) { 27 | currentState = state; 28 | Log.d(TAG, "Changed to " + toString()); 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | switch (currentState) { 34 | case IDLE: 35 | return "IDLE"; 36 | case INITIALIZED: 37 | return "INITIALIZED"; 38 | case PREPARING: 39 | return "PREPARING"; 40 | case PREPARED: 41 | return "PREPARED"; 42 | case STARTED: 43 | return "STARTED"; 44 | case PAUSED: 45 | return "PAUSED"; 46 | case STOPPED: 47 | return "STOPPED"; 48 | case PLAYBACK_COMPLETED: 49 | return "PLAYBACK_COMPLETED"; 50 | case END: 51 | return "END"; 52 | case ERROR: 53 | return "ERROR"; 54 | default: 55 | return "UNKNOWN_STATE"; 56 | } 57 | } 58 | 59 | boolean is(int state) { 60 | return currentState == state; 61 | } 62 | 63 | boolean seekingAllowed() { 64 | return is(STARTED) || is(PREPARED) || is(PAUSED) || is(PLAYBACK_COMPLETED); 65 | } 66 | 67 | boolean stoppingAllowed() { 68 | return is(PREPARED) || is(STARTED) || is(STOPPED) || is(PAUSED) || is(PLAYBACK_COMPLETED); 69 | } 70 | 71 | boolean settingDataSourceAllowed() { 72 | return is(IDLE); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /library/src/main/java/org/vinuxproject/sonic/Sonic.java: -------------------------------------------------------------------------------- 1 | /* Sonic library 2 | Copyright 2010, 2011 3 | Bill Cox 4 | This file is part of the Sonic Library. 5 | 6 | This file is licensed under the Apache 2.0 license. 7 | */ 8 | 9 | /* Minor modifications by Martin Fietz 10 | * The original source can be found here: 11 | * https://github.com/waywardgeek/sonic/blob/master/Sonic.java 12 | */ 13 | 14 | 15 | package org.vinuxproject.sonic; 16 | 17 | public class Sonic { 18 | 19 | private static final int SONIC_MIN_PITCH = 65; 20 | private static final int SONIC_MAX_PITCH = 400; 21 | /* This is used to down-sample some inputs to improve speed */ 22 | private static final int SONIC_AMDF_FREQ = 4000; 23 | 24 | private short inputBuffer[]; 25 | private short outputBuffer[]; 26 | private short pitchBuffer[]; 27 | private short downSampleBuffer[]; 28 | private float speed; 29 | private float volume; 30 | private float pitch; 31 | private float rate; 32 | private int oldRatePosition; 33 | private int newRatePosition; 34 | private boolean useChordPitch; 35 | private int quality; 36 | private int numChannels; 37 | private int inputBufferSize; 38 | private int pitchBufferSize; 39 | private int outputBufferSize; 40 | private int numInputSamples; 41 | private int numOutputSamples; 42 | private int numPitchSamples; 43 | private int minPeriod; 44 | private int maxPeriod; 45 | private int maxRequired; 46 | private int remainingInputToCopy; 47 | private int sampleRate; 48 | private int prevPeriod; 49 | private int prevMinDiff; 50 | 51 | // Resize the array. 52 | private short[] resize(short[] oldArray, 53 | int newLength) { 54 | newLength *= numChannels; 55 | short[] newArray = new short[newLength]; 56 | int length = oldArray.length <= newLength ? oldArray.length : newLength; 57 | 58 | System.arraycopy(oldArray, 0, newArray, 0, length); 59 | 60 | return newArray; 61 | } 62 | 63 | // Move samples from one array to another. May move samples down within an array, but not up. 64 | private void move(short dest[], int destPos, 65 | short source[], 66 | int sourcePos, 67 | int numSamples) { 68 | System.arraycopy(source, sourcePos * numChannels + 0, dest, destPos * numChannels + 0, 69 | numSamples * numChannels); 70 | } 71 | 72 | // Scale the samples by the factor. 73 | private void scaleSamples(short samples[], 74 | int position, 75 | int numSamples, 76 | float volume) { 77 | int fixedPointVolume = (int) (volume * 4096.0f); 78 | int start = position * numChannels; 79 | int stop = start + numSamples * numChannels; 80 | 81 | for (int xSample = start; xSample < stop; xSample++) { 82 | int value = (samples[xSample] * fixedPointVolume) >> 12; 83 | if (value > 32767) { 84 | value = 32767; 85 | } else if (value < -32767) { 86 | value = -32767; 87 | } 88 | samples[xSample] = (short) value; 89 | } 90 | } 91 | 92 | // Get the speed of the stream. 93 | public float getSpeed() { 94 | return speed; 95 | } 96 | 97 | // Set the speed of the stream. 98 | public void setSpeed(float speed) { 99 | this.speed = speed; 100 | } 101 | 102 | // Get the pitch of the stream. 103 | public float getPitch() { 104 | return pitch; 105 | } 106 | 107 | // Set the pitch of the stream. 108 | public void setPitch(float pitch) { 109 | this.pitch = pitch; 110 | } 111 | 112 | // Get the rate of the stream. 113 | public float getRate() { 114 | return rate; 115 | } 116 | 117 | // Set the playback rate of the stream. This scales pitch and speed at the same time. 118 | public void setRate(float rate) { 119 | this.rate = rate; 120 | this.oldRatePosition = 0; 121 | this.newRatePosition = 0; 122 | } 123 | 124 | // Get the vocal chord pitch setting. 125 | public boolean getChordPitch() { 126 | return useChordPitch; 127 | } 128 | 129 | // Set the vocal chord mode for pitch computation. Default is off. 130 | public void setChordPitch( 131 | boolean useChordPitch) { 132 | this.useChordPitch = useChordPitch; 133 | } 134 | 135 | // Get the quality setting. 136 | public int getQuality() { 137 | return quality; 138 | } 139 | 140 | // Set the "quality". Default 0 is virtually as good as 1, but very much faster. 141 | public void setQuality(int quality) { 142 | this.quality = quality; 143 | } 144 | 145 | // Get the scaling factor of the stream. 146 | public float getVolume() { 147 | return volume; 148 | } 149 | 150 | // Set the scaling factor of the stream. 151 | public void setVolume(float volume) { 152 | this.volume = volume; 153 | } 154 | 155 | // Allocate stream buffers. 156 | private void allocateStreamBuffers(int sampleRate, 157 | int numChannels) { 158 | minPeriod = sampleRate / SONIC_MAX_PITCH; 159 | maxPeriod = sampleRate / SONIC_MIN_PITCH; 160 | maxRequired = 2 * maxPeriod; 161 | inputBufferSize = maxRequired; 162 | inputBuffer = new short[maxRequired * numChannels]; 163 | outputBufferSize = maxRequired; 164 | outputBuffer = new short[maxRequired * numChannels]; 165 | pitchBufferSize = maxRequired; 166 | pitchBuffer = new short[maxRequired * numChannels]; 167 | downSampleBuffer = new short[maxRequired]; 168 | this.sampleRate = sampleRate; 169 | this.numChannels = numChannels; 170 | oldRatePosition = 0; 171 | newRatePosition = 0; 172 | prevPeriod = 0; 173 | } 174 | 175 | // Create a sonic stream. 176 | public Sonic(int sampleRate, 177 | int numChannels) { 178 | allocateStreamBuffers(sampleRate, numChannels); 179 | speed = 1.0f; 180 | pitch = 1.0f; 181 | volume = 1.0f; 182 | rate = 1.0f; 183 | oldRatePosition = 0; 184 | newRatePosition = 0; 185 | useChordPitch = false; 186 | quality = 0; 187 | } 188 | 189 | // Get the sample rate of the stream. 190 | public int getSampleRate() { 191 | return sampleRate; 192 | } 193 | 194 | // Set the sample rate of the stream. This will cause samples buffered in the stream to be lost. 195 | public void setSampleRate(int sampleRate) { 196 | allocateStreamBuffers(sampleRate, numChannels); 197 | } 198 | 199 | // Get the number of channels. 200 | public int getNumChannels() { 201 | return numChannels; 202 | } 203 | 204 | // Set the num channels of the stream. This will cause samples buffered in the stream to be lost. 205 | public void setNumChannels(int numChannels) { 206 | allocateStreamBuffers(sampleRate, numChannels); 207 | } 208 | 209 | // Enlarge the output buffer if needed. 210 | private void enlargeOutputBufferIfNeeded(int numSamples) { 211 | if (numOutputSamples + numSamples > outputBufferSize) { 212 | outputBufferSize += (outputBufferSize >> 1) + numSamples; 213 | outputBuffer = resize(outputBuffer, outputBufferSize); 214 | } 215 | } 216 | 217 | // Enlarge the input buffer if needed. 218 | private void enlargeInputBufferIfNeeded(int numSamples) { 219 | if (numInputSamples + numSamples > inputBufferSize) { 220 | inputBufferSize += (inputBufferSize >> 1) + numSamples; 221 | inputBuffer = resize(inputBuffer, inputBufferSize); 222 | } 223 | } 224 | 225 | // Add the input samples to the input buffer. 226 | private void addFloatSamplesToInputBuffer(float samples[], 227 | int numSamples) { 228 | if (numSamples == 0) { 229 | return; 230 | } 231 | enlargeInputBufferIfNeeded(numSamples); 232 | int xBuffer = numInputSamples * numChannels; 233 | for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { 234 | inputBuffer[xBuffer++] = (short) (samples[xSample] * 32767.0f); 235 | } 236 | numInputSamples += numSamples; 237 | } 238 | 239 | // Add the input samples to the input buffer. 240 | private void addShortSamplesToInputBuffer(short samples[], 241 | int numSamples) { 242 | if (numSamples == 0) { 243 | return; 244 | } 245 | enlargeInputBufferIfNeeded(numSamples); 246 | move(inputBuffer, numInputSamples, samples, 0, numSamples); 247 | numInputSamples += numSamples; 248 | } 249 | 250 | // Add the input samples to the input buffer. 251 | private void addUnsignedByteSamplesToInputBuffer(byte samples[], 252 | int numSamples) { 253 | short sample; 254 | 255 | enlargeInputBufferIfNeeded(numSamples); 256 | int xBuffer = numInputSamples * numChannels; 257 | for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { 258 | sample = (short) ((samples[xSample] & 0xff) - 128); // Convert from unsigned to signed 259 | inputBuffer[xBuffer++] = (short) (sample << 8); 260 | } 261 | numInputSamples += numSamples; 262 | } 263 | 264 | // Add the input samples to the input buffer. They must be 16-bit little-endian encoded in a byte array. 265 | private void addBytesToInputBuffer(byte inBuffer[], 266 | int numBytes) { 267 | int numSamples = numBytes / (2 * numChannels); 268 | short sample; 269 | 270 | enlargeInputBufferIfNeeded(numSamples); 271 | int xBuffer = numInputSamples * numChannels; 272 | for (int xByte = 0; xByte + 1 < numBytes; xByte += 2) { 273 | sample = (short) ((inBuffer[xByte] & 0xff) | (inBuffer[xByte + 1] << 8)); 274 | inputBuffer[xBuffer++] = sample; 275 | } 276 | numInputSamples += numSamples; 277 | } 278 | 279 | // Remove input samples that we have already processed. 280 | private void removeInputSamples(int position) { 281 | int remainingSamples = numInputSamples - position; 282 | 283 | move(inputBuffer, 0, inputBuffer, position, remainingSamples); 284 | numInputSamples = remainingSamples; 285 | } 286 | 287 | // Just copy from the array to the output buffer 288 | private void copyToOutput(short samples[], 289 | int position, 290 | int numSamples) { 291 | enlargeOutputBufferIfNeeded(numSamples); 292 | move(outputBuffer, numOutputSamples, samples, position, numSamples); 293 | numOutputSamples += numSamples; 294 | } 295 | 296 | // Just copy from the input buffer to the output buffer. Return num samples copied. 297 | private int copyInputToOutput(int position) { 298 | int numSamples = remainingInputToCopy; 299 | 300 | if (numSamples > maxRequired) { 301 | numSamples = maxRequired; 302 | } 303 | copyToOutput(inputBuffer, position, numSamples); 304 | remainingInputToCopy -= numSamples; 305 | return numSamples; 306 | } 307 | 308 | // Read data out of the stream. Sometimes no data will be available, and zero 309 | // is returned, which is not an error condition. 310 | public int readFloatFromStream(float samples[], 311 | int maxSamples) { 312 | int numSamples = numOutputSamples; 313 | int remainingSamples = 0; 314 | 315 | if (numSamples == 0) { 316 | return 0; 317 | } 318 | if (numSamples > maxSamples) { 319 | remainingSamples = numSamples - maxSamples; 320 | numSamples = maxSamples; 321 | } 322 | for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { 323 | samples[xSample++] = (outputBuffer[xSample]) / 32767.0f; 324 | } 325 | move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); 326 | numOutputSamples = remainingSamples; 327 | return numSamples; 328 | } 329 | 330 | // Read short data out of the stream. Sometimes no data will be available, and zero 331 | // is returned, which is not an error condition. 332 | public int readShortFromStream(short samples[], 333 | int maxSamples) { 334 | int numSamples = numOutputSamples; 335 | int remainingSamples = 0; 336 | 337 | if (numSamples == 0) { 338 | return 0; 339 | } 340 | if (numSamples > maxSamples) { 341 | remainingSamples = numSamples - maxSamples; 342 | numSamples = maxSamples; 343 | } 344 | move(samples, 0, outputBuffer, 0, numSamples); 345 | move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); 346 | numOutputSamples = remainingSamples; 347 | return numSamples; 348 | } 349 | 350 | // Read unsigned byte data out of the stream. Sometimes no data will be available, and zero 351 | // is returned, which is not an error condition. 352 | public int readUnsignedByteFromStream(byte samples[], 353 | int maxSamples) { 354 | int numSamples = numOutputSamples; 355 | int remainingSamples = 0; 356 | 357 | if (numSamples == 0) { 358 | return 0; 359 | } 360 | if (numSamples > maxSamples) { 361 | remainingSamples = numSamples - maxSamples; 362 | numSamples = maxSamples; 363 | } 364 | for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { 365 | samples[xSample] = (byte) ((outputBuffer[xSample] >> 8) + 128); 366 | } 367 | move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); 368 | numOutputSamples = remainingSamples; 369 | return numSamples; 370 | } 371 | 372 | // Read unsigned byte data out of the stream. Sometimes no data will be available, and zero 373 | // is returned, which is not an error condition. 374 | public int readBytesFromStream(byte outBuffer[], 375 | int maxBytes) { 376 | int maxSamples = maxBytes / (2 * numChannels); 377 | int numSamples = numOutputSamples; 378 | int remainingSamples = 0; 379 | 380 | if (numSamples == 0 || maxSamples == 0) { 381 | return 0; 382 | } 383 | if (numSamples > maxSamples) { 384 | remainingSamples = numSamples - maxSamples; 385 | numSamples = maxSamples; 386 | } 387 | for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { 388 | short sample = outputBuffer[xSample]; 389 | outBuffer[xSample << 1] = (byte) (sample & 0xff); 390 | outBuffer[(xSample << 1) + 1] = (byte) (sample >> 8); 391 | } 392 | move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); 393 | numOutputSamples = remainingSamples; 394 | return 2 * numSamples * numChannels; 395 | } 396 | 397 | // Force the sonic stream to generate output using whatever data it currently 398 | // has. No extra delay will be added to the output, but flushing in the middle of 399 | // words could introduce distortion. 400 | public void flushStream() { 401 | int remainingSamples = numInputSamples; 402 | float s = speed / pitch; 403 | float r = rate * pitch; 404 | int expectedOutputSamples = numOutputSamples + (int) ((remainingSamples / s + numPitchSamples) / r + 0.5f); 405 | 406 | // Add enough silence to flush both input and pitch buffers. 407 | enlargeInputBufferIfNeeded(remainingSamples + 2 * maxRequired); 408 | for (int xSample = 0; xSample < 2 * maxRequired * numChannels; xSample++) { 409 | inputBuffer[remainingSamples * numChannels + xSample] = 0; 410 | } 411 | numInputSamples += 2 * maxRequired; 412 | writeShortToStream(null, 0); 413 | // Throw away any extra samples we generated due to the silence we added. 414 | if (numOutputSamples > expectedOutputSamples) { 415 | numOutputSamples = expectedOutputSamples; 416 | } 417 | // Empty input and pitch buffers. 418 | numInputSamples = 0; 419 | remainingInputToCopy = 0; 420 | numPitchSamples = 0; 421 | } 422 | 423 | // Return the number of samples in the output buffer 424 | public int samplesAvailable() { 425 | return numOutputSamples; 426 | } 427 | 428 | // If skip is greater than one, average skip samples together and write them to 429 | // the down-sample buffer. If numChannels is greater than one, mix the channels 430 | // together as we down sample. 431 | private void downSampleInput(short samples[], 432 | int position, 433 | int skip) { 434 | int numSamples = maxRequired / skip; 435 | int samplesPerValue = numChannels * skip; 436 | int value; 437 | 438 | position *= numChannels; 439 | for (int i = 0; i < numSamples; i++) { 440 | value = 0; 441 | for (int j = 0; j < samplesPerValue; j++) { 442 | value += samples[position + i * samplesPerValue + j]; 443 | } 444 | value /= samplesPerValue; 445 | downSampleBuffer[i] = (short) value; 446 | } 447 | } 448 | 449 | // Find the best frequency match in the range, and given a sample skip multiple. 450 | // For now, just find the pitch of the first channel. Note that the contents of retDiffs 451 | // will be overwritten. 452 | private int findPitchPeriodInRange(short samples[], 453 | int position, 454 | int minPeriod, 455 | int maxPeriod, 456 | int[] retDiffs) { 457 | int bestPeriod = 0, worstPeriod = 255; 458 | int minDiff = 1, maxDiff = 0; 459 | 460 | position *= numChannels; 461 | for (int period = minPeriod; period <= maxPeriod; period++) { 462 | int diff = 0; 463 | for (int i = 0; i < period; i++) { 464 | short sVal = samples[position + i]; 465 | short pVal = samples[position + period + i]; 466 | diff += sVal >= pVal ? sVal - pVal : pVal - sVal; 467 | } 468 | /* Note that the highest number of samples we add into diff will be less 469 | than 256, since we skip samples. Thus, diff is a 24 bit number, and 470 | we can safely multiply by numSamples without overflow */ 471 | if (diff * bestPeriod < minDiff * period) { 472 | minDiff = diff; 473 | bestPeriod = period; 474 | } 475 | if (diff * worstPeriod > maxDiff * period) { 476 | maxDiff = diff; 477 | worstPeriod = period; 478 | } 479 | } 480 | retDiffs[0] = minDiff / bestPeriod; 481 | retDiffs[1] = maxDiff / worstPeriod; 482 | return bestPeriod; 483 | } 484 | 485 | // At abrupt ends of voiced words, we can have pitch periods that are better 486 | // approximated by the previous pitch period estimate. Try to detect this case. 487 | private boolean prevPeriodBetter(int period, 488 | int minDiff, 489 | int maxDiff, 490 | boolean preferNewPeriod) { 491 | if (minDiff == 0 || prevPeriod == 0) { 492 | return false; 493 | } 494 | if (preferNewPeriod) { 495 | if (maxDiff > minDiff * 3) { 496 | // Got a reasonable match this period 497 | return false; 498 | } 499 | if (minDiff * 2 <= prevMinDiff * 3) { 500 | // Mismatch is not that much greater this period 501 | return false; 502 | } 503 | } else { 504 | if (minDiff <= prevMinDiff) { 505 | return false; 506 | } 507 | } 508 | return true; 509 | } 510 | 511 | // Find the pitch period. This is a critical step, and we may have to try 512 | // multiple ways to get a good answer. This version uses AMDF. To improve 513 | // speed, we down sample by an integer factor get in the 11KHz range, and then 514 | // do it again with a narrower frequency range without down sampling 515 | private int findPitchPeriod(short samples[], 516 | int position, 517 | boolean preferNewPeriod) { 518 | int[] diffs = {0, 0}; // 0: minDiff, 1: maxDiff; for mutability 519 | int period, retPeriod; 520 | int skip = 1; 521 | 522 | if (sampleRate > SONIC_AMDF_FREQ && quality == 0) { 523 | skip = sampleRate / SONIC_AMDF_FREQ; 524 | } 525 | if (numChannels == 1 && skip == 1) { 526 | period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod, diffs); 527 | } else { 528 | downSampleInput(samples, position, skip); 529 | period = findPitchPeriodInRange(downSampleBuffer, 0, minPeriod / skip, 530 | maxPeriod / skip, diffs); 531 | if (skip != 1) { 532 | period *= skip; 533 | int minP = period - (skip << 2); 534 | int maxP = period + (skip << 2); 535 | if (minP < minPeriod) { 536 | minP = minPeriod; 537 | } 538 | if (maxP > maxPeriod) { 539 | maxP = maxPeriod; 540 | } 541 | if (numChannels == 1) { 542 | period = findPitchPeriodInRange(samples, position, minP, maxP, diffs); 543 | } else { 544 | downSampleInput(samples, position, 1); 545 | period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP, diffs); 546 | } 547 | } 548 | } 549 | if (prevPeriodBetter(period, diffs[0], diffs[1], preferNewPeriod)) { 550 | retPeriod = prevPeriod; 551 | } else { 552 | retPeriod = period; 553 | } 554 | prevMinDiff = diffs[0]; 555 | prevPeriod = period; 556 | return retPeriod; 557 | } 558 | 559 | // Overlap two sound segments, ramp the volume of one down, while ramping the 560 | // other one from zero up, and add them, storing the result at the output. 561 | private void overlapAdd(int numSamples, 562 | int numChannels, 563 | short out[], 564 | int outPos, 565 | short rampDown[], 566 | int rampDownPos, 567 | short rampUp[], 568 | int rampUpPos) { 569 | for (int i = 0; i < numChannels; i++) { 570 | int o = outPos * numChannels + i; 571 | int u = rampUpPos * numChannels + i; 572 | int d = rampDownPos * numChannels + i; 573 | for (int t = 0; t < numSamples; t++) { 574 | out[o] = (short) ((rampDown[d] * (numSamples - t) + rampUp[u] * t) / numSamples); 575 | o += numChannels; 576 | d += numChannels; 577 | u += numChannels; 578 | } 579 | } 580 | } 581 | 582 | // Overlap two sound segments, ramp the volume of one down, while ramping the 583 | // other one from zero up, and add them, storing the result at the output. 584 | private void overlapAddWithSeparation(int numSamples, 585 | int numChannels, 586 | int separation, 587 | short out[], 588 | int outPos, 589 | short rampDown[], 590 | int rampDownPos, 591 | short rampUp[], 592 | int rampUpPos) { 593 | for (int i = 0; i < numChannels; i++) { 594 | int o = outPos * numChannels + i; 595 | int u = rampUpPos * numChannels + i; 596 | int d = rampDownPos * numChannels + i; 597 | for (int t = 0; t < numSamples + separation; t++) { 598 | if (t < separation) { 599 | out[o] = (short) (rampDown[d] * (numSamples - t) / numSamples); 600 | d += numChannels; 601 | } else if (t < numSamples) { 602 | out[o] = (short) ((rampDown[d] * (numSamples - t) + rampUp[u] * (t - separation)) / numSamples); 603 | d += numChannels; 604 | u += numChannels; 605 | } else { 606 | out[o] = (short) (rampUp[u] * (t - separation) / numSamples); 607 | u += numChannels; 608 | } 609 | o += numChannels; 610 | } 611 | } 612 | } 613 | 614 | // Just move the new samples in the output buffer to the pitch buffer 615 | private void moveNewSamplesToPitchBuffer(int originalNumOutputSamples) { 616 | int numSamples = numOutputSamples - originalNumOutputSamples; 617 | 618 | if (numPitchSamples + numSamples > pitchBufferSize) { 619 | pitchBufferSize += (pitchBufferSize >> 1) + numSamples; 620 | pitchBuffer = resize(pitchBuffer, pitchBufferSize); 621 | } 622 | move(pitchBuffer, numPitchSamples, outputBuffer, originalNumOutputSamples, numSamples); 623 | numOutputSamples = originalNumOutputSamples; 624 | numPitchSamples += numSamples; 625 | } 626 | 627 | // Remove processed samples from the pitch buffer. 628 | private void removePitchSamples(int numSamples) { 629 | if (numSamples == 0) { 630 | return; 631 | } 632 | move(pitchBuffer, 0, pitchBuffer, numSamples, numPitchSamples - numSamples); 633 | numPitchSamples -= numSamples; 634 | } 635 | 636 | // Change the pitch. The latency this introduces could be reduced by looking at 637 | // past samples to determine pitch, rather than future. 638 | private void adjustPitch(int originalNumOutputSamples) { 639 | int period, newPeriod, separation; 640 | int position = 0; 641 | 642 | if (numOutputSamples == originalNumOutputSamples) { 643 | return; 644 | } 645 | moveNewSamplesToPitchBuffer(originalNumOutputSamples); 646 | while (numPitchSamples - position >= maxRequired) { 647 | period = findPitchPeriod(pitchBuffer, position, false); 648 | newPeriod = (int) (period / pitch); 649 | enlargeOutputBufferIfNeeded(newPeriod); 650 | if (pitch >= 1.0f) { 651 | overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer, 652 | position, pitchBuffer, position + period - newPeriod); 653 | } else { 654 | separation = newPeriod - period; 655 | overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples, 656 | pitchBuffer, position, pitchBuffer, position); 657 | } 658 | numOutputSamples += newPeriod; 659 | position += period; 660 | } 661 | removePitchSamples(position); 662 | } 663 | 664 | // Interpolate the new output sample. 665 | private short interpolate(short in[], 666 | int inPos, 667 | int oldSampleRate, 668 | int newSampleRate) { 669 | short left = in[inPos * numChannels]; 670 | short right = in[inPos * numChannels + numChannels]; 671 | int position = newRatePosition * oldSampleRate; 672 | int leftPosition = oldRatePosition * newSampleRate; 673 | int rightPosition = (oldRatePosition + 1) * newSampleRate; 674 | int ratio = rightPosition - position; 675 | int width = rightPosition - leftPosition; 676 | 677 | return (short) ((ratio * left + (width - ratio) * right) / width); 678 | } 679 | 680 | // Change the rate. 681 | private void adjustRate(float rate, 682 | int originalNumOutputSamples) { 683 | int newSampleRate = (int) (sampleRate / rate); 684 | int oldSampleRate = sampleRate; 685 | int position; 686 | 687 | // Set these values to help with the integer math 688 | while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) { 689 | newSampleRate >>= 1; 690 | oldSampleRate >>= 1; 691 | } 692 | if (numOutputSamples == originalNumOutputSamples) { 693 | return; 694 | } 695 | moveNewSamplesToPitchBuffer(originalNumOutputSamples); 696 | // Leave at least one pitch sample in the buffer 697 | for (position = 0; position < numPitchSamples - 1; position++) { 698 | while ((oldRatePosition + 1) * newSampleRate > newRatePosition * oldSampleRate) { 699 | enlargeOutputBufferIfNeeded(1); 700 | for (int i = 0; i < numChannels; i++) { 701 | outputBuffer[numOutputSamples * numChannels + i] = interpolate(pitchBuffer, position + i, 702 | oldSampleRate, newSampleRate); 703 | } 704 | newRatePosition++; 705 | numOutputSamples++; 706 | } 707 | oldRatePosition++; 708 | if (oldRatePosition == oldSampleRate) { 709 | oldRatePosition = 0; 710 | if (newRatePosition != newSampleRate) { 711 | System.out.printf("Assertion failed: newRatePosition != newSampleRate\n"); 712 | assert false; 713 | } 714 | newRatePosition = 0; 715 | } 716 | } 717 | removePitchSamples(position); 718 | } 719 | 720 | 721 | // Skip over a pitch period, and copy period/speed samples to the output 722 | private int skipPitchPeriod(short samples[], 723 | int position, 724 | float speed, 725 | int period) { 726 | int newSamples; 727 | 728 | if (speed >= 2.0f) { 729 | newSamples = (int) (period / (speed - 1.0f)); 730 | } else { 731 | newSamples = period; 732 | remainingInputToCopy = (int) (period * (2.0f - speed) / (speed - 1.0f)); 733 | } 734 | enlargeOutputBufferIfNeeded(newSamples); 735 | overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position, 736 | samples, position + period); 737 | numOutputSamples += newSamples; 738 | return newSamples; 739 | } 740 | 741 | // Insert a pitch period, and determine how much input to copy directly. 742 | private int insertPitchPeriod(short samples[], 743 | int position, 744 | float speed, 745 | int period) { 746 | int newSamples; 747 | 748 | if (speed < 0.5f) { 749 | newSamples = (int) (period * speed / (1.0f - speed)); 750 | } else { 751 | newSamples = period; 752 | remainingInputToCopy = (int) (period * (2.0f * speed - 1.0f) / (1.0f - speed)); 753 | } 754 | enlargeOutputBufferIfNeeded(period + newSamples); 755 | move(outputBuffer, numOutputSamples, samples, position, period); 756 | overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples, 757 | position + period, samples, position); 758 | numOutputSamples += period + newSamples; 759 | return newSamples; 760 | } 761 | 762 | // Resample as many pitch periods as we have buffered on the input. Return 0 if 763 | // we fail to resize an input or output buffer. Also scale the output by the volume. 764 | private void changeSpeed(float speed) { 765 | int numSamples = numInputSamples; 766 | int position = 0, period, newSamples; 767 | 768 | if (numInputSamples < maxRequired) { 769 | return; 770 | } 771 | do { 772 | if (remainingInputToCopy > 0) { 773 | newSamples = copyInputToOutput(position); 774 | position += newSamples; 775 | } else { 776 | period = findPitchPeriod(inputBuffer, position, true); 777 | if (speed > 1.0) { 778 | newSamples = skipPitchPeriod(inputBuffer, position, speed, period); 779 | position += period + newSamples; 780 | } else { 781 | newSamples = insertPitchPeriod(inputBuffer, position, speed, period); 782 | position += newSamples; 783 | } 784 | } 785 | } while (position + maxRequired <= numSamples); 786 | removeInputSamples(position); 787 | } 788 | 789 | // Resample as many pitch periods as we have buffered on the input. Scale the output by the volume. 790 | private void processStreamInput() { 791 | int originalNumOutputSamples = numOutputSamples; 792 | float s = speed / pitch; 793 | float r = rate; 794 | 795 | if (!useChordPitch) { 796 | r *= pitch; 797 | } 798 | if (s > 1.00001 || s < 0.99999) { 799 | changeSpeed(s); 800 | } else { 801 | copyToOutput(inputBuffer, 0, numInputSamples); 802 | numInputSamples = 0; 803 | } 804 | if (useChordPitch && pitch != 1.0f) { 805 | adjustPitch(originalNumOutputSamples); 806 | } else if (r != 1.0f) { 807 | adjustRate(r, originalNumOutputSamples); 808 | } 809 | if (volume != 1.0f) { 810 | // Adjust output volume. 811 | scaleSamples(outputBuffer, originalNumOutputSamples, numOutputSamples - originalNumOutputSamples, 812 | volume); 813 | } 814 | } 815 | 816 | // Write floating point data to the input buffer and process it. 817 | public void writeFloatToStream(float samples[], 818 | int numSamples) { 819 | addFloatSamplesToInputBuffer(samples, numSamples); 820 | processStreamInput(); 821 | } 822 | 823 | // Write the data to the input stream, and process it. 824 | public void writeShortToStream(short samples[], 825 | int numSamples) { 826 | addShortSamplesToInputBuffer(samples, numSamples); 827 | processStreamInput(); 828 | } 829 | 830 | // Simple wrapper around sonicWriteFloatToStream that does the unsigned byte to short 831 | // conversion for you. 832 | public void writeUnsignedByteToStream(byte samples[], 833 | int numSamples) { 834 | addUnsignedByteSamplesToInputBuffer(samples, numSamples); 835 | processStreamInput(); 836 | } 837 | 838 | // Simple wrapper around sonicWriteBytesToStream that does the byte to 16-bit LE conversion. 839 | public void writeBytesToStream(byte inBuffer[], 840 | int numBytes) { 841 | addBytesToInputBuffer(inBuffer, numBytes); 842 | processStreamInput(); 843 | } 844 | 845 | // This is a non-stream oriented interface to just change the speed of a sound sample 846 | public static int changeFloatSpeed(float samples[], 847 | int numSamples, 848 | float speed, 849 | float pitch, 850 | float rate, 851 | float volume, 852 | boolean useChordPitch, 853 | int sampleRate, 854 | int numChannels) { 855 | Sonic stream = new Sonic(sampleRate, numChannels); 856 | 857 | stream.setSpeed(speed); 858 | stream.setPitch(pitch); 859 | stream.setRate(rate); 860 | stream.setVolume(volume); 861 | stream.setChordPitch(useChordPitch); 862 | stream.writeFloatToStream(samples, numSamples); 863 | stream.flushStream(); 864 | numSamples = stream.samplesAvailable(); 865 | stream.readFloatFromStream(samples, numSamples); 866 | return numSamples; 867 | } 868 | 869 | /* This is a non-stream oriented interface to just change the speed of a sound sample */ 870 | public int sonicChangeShortSpeed(short samples[], 871 | int numSamples, 872 | float speed, 873 | float pitch, 874 | float rate, 875 | float volume, 876 | boolean useChordPitch, 877 | int sampleRate, 878 | int numChannels) { 879 | Sonic stream = new Sonic(sampleRate, numChannels); 880 | 881 | stream.setSpeed(speed); 882 | stream.setPitch(pitch); 883 | stream.setRate(rate); 884 | stream.setVolume(volume); 885 | stream.setChordPitch(useChordPitch); 886 | stream.writeShortToStream(samples, numSamples); 887 | stream.flushStream(); 888 | numSamples = stream.samplesAvailable(); 889 | stream.readShortFromStream(samples, numSamples); 890 | return numSamples; 891 | } 892 | } 893 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':library' 2 | --------------------------------------------------------------------------------