() {
14 |
15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AudioViewHolder {
16 | return AudioViewHolder(LayoutInflater.from(activity).inflate(R.layout.item_audio,parent,false))
17 | }
18 |
19 | override fun getItemCount(): Int {
20 | return audioList.size
21 | }
22 |
23 | override fun onBindViewHolder(holder: AudioViewHolder, position: Int) {
24 | holder.itemView.title.text = "${audioList[position].title}\n${audioList[position].artist}".trim()
25 | }
26 |
27 | inner class AudioViewHolder(view: View) : RecyclerView.ViewHolder(view) {
28 |
29 | init {
30 | view.setOnClickListener {
31 | activity.onSelectAudio(audioList[adapterPosition])
32 | }
33 | }
34 | }
35 |
36 |
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/masoudss/model/AudioModel.kt:
--------------------------------------------------------------------------------
1 | package com.masoudss.model
2 |
3 | class AudioModel {
4 |
5 | var title = ""
6 |
7 | var artist = ""
8 |
9 | var path = ""
10 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_audio.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_github.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_import.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
18 |
19 |
20 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
55 |
56 |
73 |
74 |
80 |
81 |
87 |
88 |
96 |
97 |
98 |
104 |
105 |
112 |
113 |
119 |
120 |
127 |
128 |
135 |
136 |
144 |
145 |
152 |
153 |
162 |
163 |
168 |
169 |
174 |
175 |
180 |
181 |
186 |
187 |
193 |
194 |
201 |
202 |
208 |
209 |
210 |
211 |
212 |
213 |
218 |
219 |
224 |
225 |
230 |
231 |
237 |
238 |
244 |
245 |
252 |
253 |
254 |
255 |
256 |
257 |
263 |
264 |
269 |
270 |
275 |
276 |
282 |
283 |
290 |
291 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_select_audio.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_audio.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #108BF3
4 | #108BF3
5 | #108BF3
6 |
7 | #108BF3
8 | #FE444C
9 | #FDC01F
10 | #0BCF73
11 | #f93376
12 | #ffffff
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | HankingMusic
3 | Permission Denied :(
4 | Please wait...
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.30'
5 | ext.kotlin_version = '1.3.21'
6 | repositories {
7 | google()
8 | jcenter()
9 |
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:4.0.1'
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 | maven { url 'https://jitpack.io' }
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/files/dancer.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/files/dancer.gif
--------------------------------------------------------------------------------
/files/music.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/files/music.gif
--------------------------------------------------------------------------------
/files/preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/files/preview.gif
--------------------------------------------------------------------------------
/files/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/files/preview.png
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Apr 07 18:13:15 IRDT 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/lib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/lib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android-extensions'
3 | apply plugin: 'kotlin-android'
4 |
5 | android {
6 | compileSdkVersion 30
7 | ndkVersion "16.1.4479499"
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 30
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 | implementation "androidx.appcompat:appcompat:1.2.0"
30 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
31 | implementation 'com.github.lincollincol:Amplituda:1.5'
32 | }
33 | repositories {
34 | mavenCentral()
35 | }
36 |
--------------------------------------------------------------------------------
/lib/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/lib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/DanceView.kt:
--------------------------------------------------------------------------------
1 | package com.masoudss.lib
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.*
6 | import android.util.AttributeSet
7 | import android.util.Log
8 | import com.masoudss.lib.audiovisulizer.base.BaseVisualizer
9 | import com.masoudss.lib.utils.Utils
10 |
11 | /**
12 | * create by 胡汉君 date 2021/4/7-18-23
13 | *email :huhanjun@bytedance.com
14 | */
15 |
16 | /***********************************************************
17 | * * Copyright (C), 2020-2030, Bytedance edu Corp., Ltd.
18 | * * File: - DanceView.kt
19 | * * Description: build this module.
20 | * * Date : 2021/4/7-18-23
21 | * * Author: 胡汉君@Apps.minddance
22 |
23 | ****************************************************************/
24 | class DanceView @JvmOverloads constructor(
25 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
26 | ) : BaseVisualizer(context, attrs, defStyleAttr) {
27 |
28 | private lateinit var mPaint: Paint//画笔
29 | private var danceColor = Color.YELLOW
30 |
31 | /**
32 | * 画布的宽度
33 | */
34 | private var mCanvasWidth = 0
35 |
36 | /**
37 | *画布的高度
38 | */
39 | private var mCanvasHeight = 0
40 |
41 | /**
42 | *音频块高度
43 | */
44 | private var danceHeight = Utils.dp(context, 4).toInt()
45 |
46 | /**
47 | *音频块的宽度
48 | */
49 | private var danceWidth = Utils.dp(context, 12).toInt()
50 | private var miniNum = 1
51 | private var maxNum = 40
52 | private val mDanceRect = RectF()
53 | private var colorStart = Color.WHITE
54 | private var colorCenter = Color.YELLOW
55 | private var colorEnd = Color.WHITE
56 |
57 | /**
58 | *默认的渐变个数
59 | */
60 | private var shaderNum = 3
61 |
62 | /**
63 | * 获取可以用的宽度
64 | */
65 | private fun getAvailableWith() = mCanvasWidth - paddingLeft - paddingRight
66 |
67 | /**
68 | *获取可以用的高度
69 | */
70 | private fun getAvailableHeight() = mCanvasHeight - paddingTop - paddingBottom
71 |
72 | /**
73 | *音频块之间的间距
74 | */
75 | var danceGap: Float = Utils.dp(context, 4)
76 | set(value) {
77 | field = value
78 | invalidate()
79 | }
80 |
81 | init {
82 | val ta = context.obtainStyledAttributes(attrs, R.styleable.DanceView, 0, 0)
83 | danceColor = ta.getColor(R.styleable.DanceView_dance_color, danceColor)
84 | danceWidth =
85 | ta.getDimension(R.styleable.DanceView_dance_width, danceWidth.toFloat()).toInt()
86 | danceGap = ta.getDimension(R.styleable.DanceView_dance_gap, danceGap)
87 | colorStart = ta.getColor(R.styleable.DanceView_color_start, colorStart)
88 | colorEnd = ta.getColor(R.styleable.DanceView_color_end, colorEnd)
89 | colorCenter = ta.getColor(R.styleable.DanceView_color_center, colorCenter)
90 | shaderNum = ta.getInteger(R.styleable.DanceView_shader_num, shaderNum)
91 | maxNum = ta.getInteger(R.styleable.DanceView_max_dance_num, maxNum)
92 | miniNum = ta.getInteger(R.styleable.DanceView_min_dance_num, miniNum)
93 | ta.recycle()
94 | init()
95 | }
96 |
97 | private fun init() {
98 | mPaint = Paint()//初始化画笔工具
99 | mPaint.isAntiAlias = true//抗锯齿
100 | mPaint.color = danceColor//画笔颜色
101 | mPaint.strokeJoin = Paint.Join.ROUND//频块圆角
102 | mPaint.strokeCap = Paint.Cap.ROUND//频块圆角
103 | mPaint.strokeWidth = 10F
104 | }
105 |
106 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
107 | super.onSizeChanged(w, h, oldw, oldh)
108 | mCanvasWidth = w
109 | mCanvasHeight = h
110 | }
111 |
112 | @SuppressLint("DrawAllocation")
113 | override fun onDraw(canvas: Canvas?) {
114 | super.onDraw(canvas)
115 | Log.d(TAG, "mRawAudioBytes ---- mRawAudioBytes size ${mRawAudioBytes?.size}")
116 | /**
117 | * 先计算当前宽度能够放下多少个音频块
118 | */
119 | val widthNum = (getAvailableWith() / (danceGap + danceWidth)).toInt()
120 | Log.d(
121 | TAG,
122 | "widthNum $widthNum"
123 | )
124 | /**
125 | * 算出横向能放多少后,进行绘制
126 | */
127 |
128 | /**
129 | * 绘制的时候用于标记开始绘制的位置
130 | */
131 | var lastDanceRight = paddingLeft.toFloat()
132 | if (widthNum > 0 && mRawAudioBytes != null && mRawAudioBytes.isNotEmpty())
133 | for (i in 0 until widthNum) {
134 | //先算出当前高度,然后再算这个高度能放下多少个音频块
135 | val num = (mCanvasHeight / (danceHeight + danceGap)).toInt()
136 | val index = (mRawAudioBytes.size) * (i.toFloat() / widthNum)
137 | val b = (mRawAudioBytes[index.toInt()] + 128).toFloat() / 255f
138 | var heightNum =
139 | (b * num).toInt()
140 | if (heightNum < miniNum) {
141 | heightNum = miniNum
142 | }
143 | if (heightNum > maxNum) {
144 | heightNum = maxNum
145 | }
146 | //拿到最顶部的高度
147 | var lastHeight = mCanvasHeight - paddingStart.toFloat()
148 | Log.d(
149 | TAG,
150 | "heightNum $heightNum lastHeight $lastHeight lastDanceRight $lastDanceRight ${mRawAudioBytes[i]} $num $b $index"
151 | )
152 | for (j in 0 until heightNum) {
153 | mDanceRect.set(
154 | lastDanceRight,
155 | lastHeight - danceHeight,
156 | lastDanceRight + danceWidth,
157 | lastHeight
158 | )
159 | mPaint.shader = null
160 | if (j >= heightNum - shaderNum) {
161 | val backGradient = LinearGradient(
162 | lastDanceRight,
163 | lastHeight - danceHeight,
164 | lastDanceRight + danceWidth,
165 | lastHeight,
166 | intArrayOf(colorStart, colorCenter, colorEnd),
167 | null,
168 | Shader.TileMode.CLAMP
169 | )
170 | mPaint.shader = backGradient
171 | }
172 | canvas?.drawRoundRect(mDanceRect, 8f, 8f, mPaint)
173 | lastHeight -= (danceHeight + danceGap)
174 | }
175 | lastDanceRight += danceWidth + danceGap
176 | }
177 |
178 | }
179 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/SeekBarOnProgressChanged.kt:
--------------------------------------------------------------------------------
1 | package com.masoudss.lib
2 |
3 | interface SeekBarOnProgressChanged {
4 |
5 | fun onProgressChanged(waveformSeekBar: WaveformSeekBar, progress: Int, fromUser: Boolean)
6 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/WaveformSeekBar.kt:
--------------------------------------------------------------------------------
1 | package com.masoudss.lib
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.*
6 | import android.util.AttributeSet
7 | import android.util.Log
8 | import android.view.MotionEvent
9 | import android.view.View
10 | import android.view.ViewConfiguration
11 | import com.masoudss.lib.audiovisulizer.base.BaseVisualizer
12 | import com.masoudss.lib.exception.SampleDataException
13 | import com.masoudss.lib.utils.ThreadBlocking
14 | import com.masoudss.lib.utils.Utils
15 | import com.masoudss.lib.utils.WaveGravity
16 | import com.masoudss.lib.utils.WaveformOptions
17 | import java.io.File
18 | import kotlin.math.abs
19 |
20 | class WaveformSeekBar : BaseVisualizer {
21 |
22 | private var mCanvasWidth = 0
23 | private var mCanvasHeight = 0
24 |
25 | private val mWavePaint = Paint(Paint.ANTI_ALIAS_FLAG)
26 | private val mWaveRect = RectF()
27 | private val mProgressCanvas = Canvas()
28 | private var mMaxValue = Utils.dp(context, 2).toInt()
29 | private var mTouchDownX = 0F
30 | private var mScaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
31 |
32 | constructor(context: Context?) : super(context) {
33 | init(null)
34 | }
35 |
36 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
37 | init(attrs)
38 | }
39 |
40 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
41 | context,
42 | attrs,
43 | defStyleAttr
44 | ) {
45 | init(attrs)
46 | }
47 |
48 | private fun init(attrs: AttributeSet?) {
49 |
50 | val ta = context.obtainStyledAttributes(attrs, R.styleable.WaveformSeekBar)
51 |
52 | waveWidth = ta.getDimension(R.styleable.WaveformSeekBar_wave_width, waveWidth)
53 | waveGap = ta.getDimension(R.styleable.WaveformSeekBar_wave_gap, waveGap)
54 | waveCornerRadius =
55 | ta.getDimension(R.styleable.WaveformSeekBar_wave_corner_radius, waveCornerRadius)
56 | waveMinHeight = ta.getDimension(R.styleable.WaveformSeekBar_wave_min_height, waveMinHeight)
57 | waveBackgroundColor =
58 | ta.getColor(R.styleable.WaveformSeekBar_wave_background_color, waveBackgroundColor)
59 | waveProgressColor =
60 | ta.getColor(R.styleable.WaveformSeekBar_wave_progress_color, waveProgressColor)
61 | progress = ta.getInteger(R.styleable.WaveformSeekBar_wave_progress, progress)
62 | maxProgress = ta.getFloat(R.styleable.WaveformSeekBar_wave_max_progress, maxProgress)
63 | val gravity = ta.getString(R.styleable.WaveformSeekBar_wave_gravity)
64 | waveGravity = when (gravity) {
65 | "1" -> WaveGravity.TOP
66 | "2" -> WaveGravity.CENTER
67 | else -> WaveGravity.BOTTOM
68 | }
69 |
70 | ta.recycle()
71 | WaveformOptions.init(context)
72 | }
73 |
74 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
75 | super.onSizeChanged(w, h, oldw, oldh)
76 | mCanvasWidth = w
77 | mCanvasHeight = h
78 | }
79 |
80 | @SuppressLint("DrawAllocation")
81 | override fun onDraw(canvas: Canvas) {
82 | super.onDraw(canvas)
83 | Log.d(
84 | TAG,
85 | "mRawAudioBytes ---- mRawAudioBytes size ${mRawAudioBytes?.size}"
86 | )
87 | if (sample == null || sample!!.isEmpty())
88 | throw SampleDataException()
89 |
90 | mMaxValue = sample!!.max()!!
91 | val step = (getAvailableWith() / (waveGap + waveWidth)) / sample!!.size
92 |
93 | var i = 0F
94 | var lastWaveRight = paddingLeft.toFloat()
95 | while (i < sample!!.size && mRawAudioBytes != null && i < mRawAudioBytes.size) {
96 | val height = (mRawAudioBytes[i.toInt()] + 128) / 128 * sample!![i.toInt()]
97 | var waveHeight = getAvailableHeight() * (height.toFloat() / mMaxValue)
98 | Log.d(TAG, "waveHeight $waveHeight")
99 | if (waveHeight < waveMinHeight)
100 | waveHeight = waveMinHeight
101 |
102 | val top: Float = when (waveGravity) {
103 | WaveGravity.TOP -> paddingTop.toFloat()
104 | WaveGravity.CENTER -> paddingTop + getAvailableHeight() / 2F - waveHeight / 2F
105 | WaveGravity.BOTTOM -> mCanvasHeight - paddingBottom - waveHeight
106 | }
107 |
108 | mWaveRect.set(lastWaveRight, top, lastWaveRight + waveWidth, top + waveHeight)
109 |
110 | when {
111 | mWaveRect.contains(
112 | getAvailableWith() * progress / maxProgress,
113 | mWaveRect.centerY()
114 | ) -> {
115 | var bitHeight = mWaveRect.height().toInt()
116 | if (bitHeight <= 0)
117 | bitHeight = waveWidth.toInt()
118 |
119 | val bitmap =
120 | Bitmap.createBitmap(getAvailableWith(), bitHeight, Bitmap.Config.ARGB_8888)
121 | mProgressCanvas.setBitmap(bitmap)
122 |
123 | val fillWidth = (getAvailableWith() * progress / maxProgress)
124 |
125 | mWavePaint.color = waveProgressColor
126 | mProgressCanvas.drawRect(0F, 0F, fillWidth, mWaveRect.bottom, mWavePaint)
127 |
128 | mWavePaint.color = waveBackgroundColor
129 | mProgressCanvas.drawRect(
130 | fillWidth,
131 | 0F,
132 | getAvailableWith().toFloat(),
133 | mWaveRect.bottom,
134 | mWavePaint
135 | )
136 |
137 | val shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
138 | mWavePaint.shader = shader
139 | }
140 | mWaveRect.right <= getAvailableWith() * progress / maxProgress -> {
141 | mWavePaint.color = waveProgressColor
142 | mWavePaint.shader = null
143 | }
144 | else -> {
145 | mWavePaint.color = waveBackgroundColor
146 | mWavePaint.shader = null
147 | }
148 | }
149 |
150 | canvas.drawRoundRect(mWaveRect, waveCornerRadius, waveCornerRadius, mWavePaint)
151 |
152 | lastWaveRight = mWaveRect.right + waveGap
153 |
154 | if (lastWaveRight + waveWidth > getAvailableWith() + paddingLeft)
155 | break
156 |
157 | i += 1 / step
158 | }
159 | }
160 |
161 | override fun onTouchEvent(event: MotionEvent?): Boolean {
162 | if (!isEnabled)
163 | return false
164 |
165 | when (event!!.action) {
166 | MotionEvent.ACTION_DOWN -> {
167 | if (isParentScrolling())
168 | mTouchDownX = event.x
169 | else
170 | updateProgress(event)
171 | }
172 | MotionEvent.ACTION_MOVE -> {
173 | updateProgress(event)
174 | }
175 | MotionEvent.ACTION_UP -> {
176 | if (abs(event.x - mTouchDownX) > mScaledTouchSlop)
177 | updateProgress(event)
178 |
179 | performClick()
180 | }
181 | }
182 | return true
183 | }
184 |
185 |
186 | private fun isParentScrolling(): Boolean {
187 | var parent = parent as View
188 | val root = rootView
189 |
190 | while (true) {
191 | when {
192 | parent.canScrollHorizontally(1) -> return true
193 | parent.canScrollHorizontally(-1) -> return true
194 | parent.canScrollVertically(1) -> return true
195 | parent.canScrollVertically(-1) -> return true
196 | }
197 |
198 | if (parent == root)
199 | return false
200 |
201 | parent = parent.parent as View
202 |
203 | }
204 | }
205 |
206 | private fun updateProgress(event: MotionEvent?) {
207 |
208 | progress = (maxProgress * event!!.x / getAvailableWith()).toInt()
209 | invalidate()
210 |
211 | if (onProgressChanged != null)
212 | onProgressChanged!!.onProgressChanged(this, progress, true)
213 | }
214 |
215 | override fun performClick(): Boolean {
216 | super.performClick()
217 | return true
218 | }
219 |
220 | private fun getAvailableWith() = mCanvasWidth - paddingLeft - paddingRight
221 | private fun getAvailableHeight() = mCanvasHeight - paddingTop - paddingBottom
222 |
223 | var onProgressChanged: SeekBarOnProgressChanged? = null
224 |
225 | var sample: IntArray? = null
226 | set(value) {
227 | field = value
228 | invalidate()
229 | }
230 |
231 | var progress: Int = 0
232 | set(value) {
233 | field = value
234 | invalidate()
235 |
236 | if (onProgressChanged != null)
237 | onProgressChanged!!.onProgressChanged(this, progress, false)
238 | }
239 |
240 | var maxProgress: Float = 100F
241 | set(value) {
242 | field = value
243 | invalidate()
244 | }
245 |
246 | var waveBackgroundColor: Int = Color.LTGRAY
247 | set(value) {
248 | field = value
249 | invalidate()
250 | }
251 |
252 | var waveProgressColor: Int = Color.WHITE
253 | set(value) {
254 | field = value
255 | invalidate()
256 | }
257 |
258 | var waveGap: Float = Utils.dp(context, 2)
259 | set(value) {
260 | field = value
261 | invalidate()
262 | }
263 |
264 | var waveWidth: Float = Utils.dp(context, 5)
265 | set(value) {
266 | field = value
267 | invalidate()
268 | }
269 |
270 | var waveMinHeight: Float = waveWidth
271 | set(value) {
272 | field = value
273 | invalidate()
274 | }
275 |
276 | var waveCornerRadius: Float = Utils.dp(context, 2)
277 | set(value) {
278 | field = value
279 | invalidate()
280 | }
281 |
282 | var waveGravity: WaveGravity = WaveGravity.CENTER
283 | set(value) {
284 | field = value
285 | invalidate()
286 | }
287 |
288 | @ThreadBlocking
289 | fun setSampleFrom(audio: File) = setSampleFrom(audio.path)
290 |
291 | @ThreadBlocking
292 | fun setSampleFrom(path: String) {
293 | WaveformOptions.getSampleFrom(path) {
294 | sample = it
295 | }
296 | }
297 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/audiovisulizer/base/BaseVisualizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Gaurav Kumar
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package com.masoudss.lib.audiovisulizer.base;
17 |
18 | import android.content.Context;
19 | import android.graphics.Color;
20 | import android.media.audiofx.Visualizer;
21 | import android.util.AttributeSet;
22 | import android.view.View;
23 |
24 | import androidx.annotation.Nullable;
25 |
26 | import com.masoudss.lib.audiovisulizer.model.AnimSpeed;
27 |
28 | import java.util.Random;
29 |
30 | /**
31 | * Base class for the visualizers
32 | *
33 | * Created by gk
34 | */
35 |
36 | abstract public class BaseVisualizer extends View {
37 | protected static String TAG = "BaseVisualizer";
38 | protected byte[] mRawAudioBytes;
39 | protected Visualizer mVisualizer;
40 | protected AnimSpeed mAnimSpeed = AnimSpeed.MEDIUM;
41 | protected boolean isVisualizationEnabled = true;
42 |
43 | public BaseVisualizer(Context context) {
44 | super(context);
45 | init(context, null);
46 | }
47 |
48 | public BaseVisualizer(Context context, @Nullable AttributeSet attrs) {
49 | super(context, attrs);
50 | init(context, attrs);
51 | }
52 |
53 | public BaseVisualizer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
54 | super(context, attrs, defStyleAttr);
55 | init(context, attrs);
56 | }
57 |
58 | private void init(Context context, AttributeSet attrs) {
59 |
60 | }
61 |
62 | /**
63 | * Sets the Animation speed of the visualization{@link AnimSpeed}
64 | *
65 | * @param animSpeed speed of the animation
66 | */
67 | public void setAnimationSpeed(AnimSpeed animSpeed) {
68 | this.mAnimSpeed = animSpeed;
69 | }
70 |
71 |
72 | /**
73 | * Sets the audio bytes to be visualized form {@link Visualizer} or other sources
74 | *
75 | * @param bytes of the raw bytes of music
76 | */
77 | public void setRawAudioBytes(byte[] bytes) {
78 | this.mRawAudioBytes = bytes;
79 | this.invalidate();
80 | }
81 |
82 | /**
83 | * Sets the audio session id for the currently playing audio
84 | *
85 | * @param audioSessionId of the media to be visualised
86 | */
87 | public void setAudioSessionId(int audioSessionId) {
88 | if (mVisualizer != null)
89 | release();
90 |
91 | mVisualizer = new Visualizer(audioSessionId);
92 | mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[0]);
93 |
94 | mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
95 | @Override
96 | public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes,
97 | int samplingRate) {
98 | BaseVisualizer.this.mRawAudioBytes = bytes;
99 | invalidate();
100 | }
101 |
102 | @Override
103 | public void onFftDataCapture(Visualizer visualizer, byte[] bytes,
104 | int samplingRate) {
105 | }
106 | }, Visualizer.getMaxCaptureRate() / 2, true, false);
107 |
108 | mVisualizer.setEnabled(true);
109 | }
110 |
111 | /**
112 | * Releases the visualizer
113 | */
114 | public void release() {
115 | if (mVisualizer != null)
116 | mVisualizer.release();
117 | }
118 |
119 | /**
120 | * Enable Visualization
121 | */
122 | public void show() {
123 | this.isVisualizationEnabled = true;
124 | }
125 |
126 | /**
127 | * @return 随机生成颜色
128 | */
129 | public int getRandomColor() {
130 | Random random = new Random();
131 | int r = random.nextInt(256);
132 | int g = random.nextInt(256);
133 | int b = random.nextInt(256);
134 | return Color.rgb(r, g, b);
135 | }
136 |
137 | /**
138 | * Disable Visualization
139 | */
140 | public void hide() {
141 | this.isVisualizationEnabled = false;
142 | }
143 |
144 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/audiovisulizer/model/AnimSpeed.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Gaurav Kumar
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package com.masoudss.lib.audiovisulizer.model;
17 |
18 | public enum AnimSpeed {
19 | SLOW,
20 | MEDIUM,
21 | FAST
22 | }
23 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/audiovisulizer/model/PaintStyle.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Gaurav Kumar
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package com.masoudss.lib.audiovisulizer.model;
17 |
18 | public enum PaintStyle {
19 | OUTLINE,
20 | FILL
21 | }
22 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/audiovisulizer/model/PositionGravity.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Gaurav Kumar
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package com.masoudss.lib.audiovisulizer.model;
17 |
18 | public enum PositionGravity {
19 | TOP,
20 | BOTTOM
21 | }
22 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/audiovisulizer/utils/AVConstants.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Gaurav Kumar
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package com.masoudss.lib.audiovisulizer.utils;
17 |
18 | import android.graphics.Color;
19 |
20 | public class AVConstants {
21 | public static final float DEFAULT_DENSITY = 0.25f;
22 | public static final int DEFAULT_COLOR = Color.BLACK;
23 | public static final float DEFAULT_STROKE_WIDTH = 6.0f;
24 | public static final int MAX_ANIM_BATCH_COUNT = 4;
25 | }
26 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/audiovisulizer/utils/BezierSpline.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Gaurav Kumar
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package com.masoudss.lib.audiovisulizer.utils;
17 |
18 | import android.graphics.PointF;
19 |
20 | public class BezierSpline {
21 |
22 | private final int nSize;
23 | private final PointF[] firstControlPoints, secondControlPoints;
24 |
25 | public BezierSpline(int size) {
26 | this.nSize = size - 1;
27 | firstControlPoints = new PointF[nSize];
28 | secondControlPoints = new PointF[nSize];
29 | for (int i = 0; i < nSize; i++) {
30 | firstControlPoints[i] = new PointF();
31 | secondControlPoints[i] = new PointF();
32 | }
33 | }
34 |
35 | /**
36 | * Get open-ended bezier spline control points.
37 | *
38 | * @param knots bezier spline points
39 | * @throws IllegalArgumentException if less than two knots are passed.
40 | */
41 | public void updateCurveControlPoints(PointF[] knots) {
42 | if (knots == null || knots.length < 2) {
43 | throw new IllegalArgumentException("At least two knot points are required");
44 | }
45 |
46 | final int n = knots.length - 1;
47 |
48 | // Special case: bezier curve should be a straight line
49 | if (n == 1) {
50 | // 3P1 = 2P0 + P3
51 | float x = (2 * knots[0].x + knots[1].x) / 3;
52 | float y = (2 * knots[0].y + knots[1].y) / 3;
53 |
54 | firstControlPoints[0].x = x;
55 | firstControlPoints[0].y = y;
56 |
57 | // P2 = 2P1 - P0
58 | x = 2 * firstControlPoints[0].x - knots[0].x;
59 | y = 2 * firstControlPoints[0].y - knots[0].y;
60 |
61 | secondControlPoints[0].x = x;
62 | secondControlPoints[0].y = y;
63 |
64 | } else {
65 |
66 | // Calculate first bezier control points
67 | // Right hand side vector
68 | float[] rhs = new float[n];
69 |
70 | // Set right hand side X values
71 | for (int i = 1; i < n - 1; i++) {
72 | rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x;
73 | }
74 | rhs[0] = knots[0].x + 2 * knots[1].x;
75 | rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2f;
76 |
77 | // Get first control points X-values
78 | float[] x = getFirstControlPoints(rhs);
79 |
80 | // Set right hand side Y values
81 | for (int i = 1; i < n - 1; i++) {
82 | rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y;
83 | }
84 | rhs[0] = knots[0].y + 2 * knots[1].y;
85 | rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2f;
86 |
87 | // Get first control points Y-values
88 | float[] y = getFirstControlPoints(rhs);
89 |
90 | for (int i = 0; i < n; i++) {
91 | // First control point
92 | firstControlPoints[i].x = x[i];
93 | firstControlPoints[i].y = y[i];
94 |
95 | // Second control point
96 | if (i < n - 1) {
97 | float xx = 2 * knots[i + 1].x - x[i + 1];
98 | float yy = 2 * knots[i + 1].y - y[i + 1];
99 | secondControlPoints[i].x = xx;
100 | secondControlPoints[i].y = yy;
101 | } else {
102 | float xx = (knots[n].x + x[n - 1]) / 2;
103 | float yy = (knots[n].y + y[n - 1]) / 2;
104 | secondControlPoints[i].x = xx;
105 | secondControlPoints[i].y = yy;
106 | }
107 | }
108 | }
109 | }
110 |
111 | /**
112 | * Solves a tridiagonal system for one of coordinates (x or y) of first
113 | * bezier control points.
114 | *
115 | * @param rhs right hand side vector.
116 | * @return Solution vector.
117 | */
118 | private float[] getFirstControlPoints(float[] rhs) {
119 | int n = rhs.length;
120 | float[] x = new float[n]; // Solution vector
121 | float[] tmp = new float[n]; // Temp workspace
122 |
123 | float b = 2.0f;
124 | x[0] = rhs[0] / b;
125 |
126 | // Decomposition and forward substitution
127 | for (int i = 1; i < n; i++) {
128 | tmp[i] = 1 / b;
129 | b = (i < n - 1 ? 4.0f : 3.5f) - tmp[i];
130 | x[i] = (rhs[i] - x[i - 1]) / b;
131 | }
132 |
133 | // Backsubstitution
134 | for (int i = 1; i < n; i++) {
135 | x[n - i - 1] -= tmp[n - i] * x[n - i];
136 | }
137 |
138 | return x;
139 | }
140 |
141 | public PointF[] getFirstControlPoints() {
142 | return firstControlPoints;
143 | }
144 |
145 | public PointF[] getSecondControlPoints() {
146 | return secondControlPoints;
147 | }
148 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/exception/InvalidInputException.kt:
--------------------------------------------------------------------------------
1 | package com.masoudss.lib.exception
2 |
3 | internal class InvalidInputException(message: String) : Exception(message)
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/exception/SampleDataException.kt:
--------------------------------------------------------------------------------
1 | package com.masoudss.lib.exception
2 |
3 | internal class SampleDataException : Exception("Set the sample data by using WaveformSeekBar.setSample method.")
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/utils/AudioPlayer.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Gaurav Kumar
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package com.masoudss.lib.utils;
17 |
18 | import android.content.Context;
19 | import android.media.MediaPlayer;
20 |
21 | public class AudioPlayer {
22 |
23 | private MediaPlayer mMediaPlayer;
24 |
25 | public void stop() {
26 | if (mMediaPlayer != null) {
27 | mMediaPlayer.release();
28 | mMediaPlayer = null;
29 | }
30 | }
31 |
32 | public void play(Context c, int rid, final AudioPlayerEvent audioPlayerEvent) {
33 | stop();
34 |
35 | mMediaPlayer = MediaPlayer.create(c, rid);
36 | mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
37 | @Override
38 | public void onCompletion(MediaPlayer mediaPlayer) {
39 | stop();
40 | if (audioPlayerEvent != null)
41 | audioPlayerEvent.onCompleted();
42 | }
43 | });
44 |
45 | mMediaPlayer.start();
46 | }
47 |
48 | public int getAudioSessionId() {
49 | if (mMediaPlayer == null)
50 | return -1;
51 | return mMediaPlayer.getAudioSessionId();
52 | }
53 |
54 | public interface AudioPlayerEvent {
55 | void onCompleted();
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/utils/ThreadBlocking.kt:
--------------------------------------------------------------------------------
1 | package com.masoudss.lib.utils
2 |
3 | internal annotation class ThreadBlocking
4 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/utils/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.masoudss.lib.utils
2 |
3 | import android.content.Context
4 | import android.util.TypedValue
5 |
6 | object Utils {
7 |
8 | fun dp(context: Context?, dp: Int): Float {
9 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), context!!.resources.displayMetrics)
10 | }
11 |
12 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/utils/WaveGravity.kt:
--------------------------------------------------------------------------------
1 | package com.masoudss.lib.utils
2 |
3 | enum class WaveGravity {
4 | TOP,
5 | CENTER,
6 | BOTTOM
7 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/masoudss/lib/utils/WaveformOptions.kt:
--------------------------------------------------------------------------------
1 | package com.masoudss.lib.utils
2 |
3 | import android.content.Context
4 | import linc.com.amplituda.Amplituda
5 | import java.io.File
6 |
7 | internal object WaveformOptions {
8 |
9 | private var amplituda: Amplituda? = null
10 |
11 | @JvmStatic
12 | fun init(context: Context) {
13 | if(amplituda == null) {
14 | amplituda = Amplituda(context)
15 | }
16 | }
17 |
18 | @JvmStatic
19 | fun getSampleFrom(file: File, onSuccess:(samples: IntArray) -> Unit) {
20 | amplituda!!.fromFile(file)
21 | .amplitudesAsList {
22 | onSuccess(it.toIntArray())
23 | }
24 | }
25 |
26 | @JvmStatic
27 | fun getSampleFrom(path: String, onSuccess: (IntArray) -> Unit) {
28 | amplituda!!.fromPath(path)
29 | .amplitudesAsList {
30 | onSuccess(it.toIntArray())
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/lib/src/main/res/raw/sample2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/lib/src/main/res/raw/sample2.mp3
--------------------------------------------------------------------------------
/lib/src/main/res/raw/sample3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/lib/src/main/res/raw/sample3.mp3
--------------------------------------------------------------------------------
/lib/src/main/res/raw/sample5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hankinghu/AudioVisulizer/e2fedd91ef26824475ec1be228d9f348960164e2/lib/src/main/res/raw/sample5.mp3
--------------------------------------------------------------------------------
/lib/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/lib/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | lib
3 |
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':lib'
2 |
--------------------------------------------------------------------------------