object AmplitudeDecodeUtil {
private const val TAG = "AmplitudeDecodeUtil"
/**
* The same path corresponds to the same amplitudeListUtil.
*/
@JvmStatic
private var ampTaskMap = HashMap<String, ArrayList<AmpTaskData>>()
/**
* getAmplitudeByType
*/
@JvmStatic
fun getAmplitudeByType(
path: String,
playUri: Uri,
isRecycle: Boolean,
ampTypeOrSegments: String,
decodeFinishCallback: (waveDataList: List<Int>?) -> Unit
) {
val isExistTask = addGetAmplitudeTask(
path,
playUri,
isRecycle,
ampTypeOrSegments,
decodeFinishCallback = { decodeFinishCallback }
)
if (isExistTask) {
DebugUtil.d(TAG, "getAmplitudeByType exist task path:$path")
return
}
CoroutineUtils.doInIOThread({
// It will be time-consuming.
ampTaskMap.getAmplitudeListUtilByPath(path)?.apply {
val currentTime = System.currentTimeMillis()
ampList.also {
val getAmpListTime = System.currentTimeMillis()
DebugUtil.d(TAG, "getAmplitudeByType getAmpList cost ${getAmpListTime - currentTime}ms")
if (it.isNullOrEmpty().not()) {
/**
* ->source one: json file or mp3 file customTag.
* If the data is cached, directly return the amplitude data.
*/
if (ampStringFromMp3 != null) {
// amplitude data from customTag. Asynchronously update amp_file_path, amp file
asyncUpdateAmpFileAndDB(path, playUri, convertStringToInt(ampStringFromMp3))
}
dispatchReadyOrFinishCallBack(path = path, soundFile = null, waveDataMap = it)
} else {
/**
* ->source two: decode file.
* Use audio file samples to parse the recorded waveform and assemble the data.
*/
decodeFile(this, path, playUri)
}
releaseAmplitudeListUtilByPath(path)
}
}
}, CoroutineScope(Dispatchers.IO))
}
/**
* Decode the file
* @param amplitudeListUtil
*/
@JvmStatic
fun decodeFile(
amplitudeListUtil: AmplitudeListUtil,
path: String,
playUri: Uri,
) {
amplitudeListUtil.apply {
releaseMp3()
DebugUtil.d(TAG, "xak2 getAmplitudeByType s")
val nowTime = System.currentTimeMillis()
setDecodeReady { _, soundFile ->
DebugUtil.w(
TAG,
"xak2 getAmplitudeByType setDecodeReady soundFile.amplitudeList.size:${soundFile.amplitudeList.size} cost:${System.currentTimeMillis() - nowTime}!"
)
dispatchReadyOrFinishCallBack(false,path, soundFile,null)
}
setDecodeFinish { ampString ->
DebugUtil.w(TAG, "xak2 getAmplitudeByType setDecodeFinish! length:${convertStringToInt(ampString).size} cost:${System.currentTimeMillis() - nowTime}")
if (ampString.isNullOrEmpty()) {
DebugUtil.w(TAG, "getAmplitudeByType ampString is NullOrEmpty !")
dispatchReadyOrFinishCallBack(path = path, soundFile = null, waveDataMap = null)
} else {
handlerAmp(path, playUri, ampString)
}
release()
}
getAmpFromSoundFile()
}
}
@JvmStatic
fun updateAmpFileAndDB(
path: String,
uri: Uri,
amplitudeList: List<Int>
) {
if (!AmpFileUtil.ampFileIsExists(BaseApplication.getAppContext(), path)) {
DebugUtil.d(TAG, "updateAmpFileAndDB start path:$path")
AmplitudeListUtil.writeAmpData(
BaseApplication.getAppContext(),
path,
uri,
amplitudeList
)
} else {
DebugUtil.d(TAG, "updateAmpFileAndDB ampFile exists")
}
}
/**
* handlerAmp
*/
@JvmStatic
fun handlerAmp(
path: String,
playUri: Uri,
ampString: String,
) {
val originalAmpIntList = convertStringToInt(ampString)
val waveDataMap = WaveDataMap(originalAmpIntList)
dispatchReadyOrFinishCallBack(path = path, soundFile = null, waveDataMap = waveDataMap)
updateAmpFileAndDB(
path,
playUri,
originalAmpIntList
)
}
/**
* asyncUpdateAmpFileAndDB
*/
@JvmStatic
fun asyncUpdateAmpFileAndDB(
path: String,
uri: Uri,
amplitudeList: List<Int>
) {
CoroutineUtils.doInIOThread({
updateAmpFileAndDB(
path,
uri,
amplitudeList
)
}, CoroutineScope(Dispatchers.IO))
}
/**
* To dispatch CallBack according to path and remove task,or dispatch CallBack according to path and remove task
*/
@JvmStatic
fun dispatchReadyOrFinishCallBack(
isFinishCallBack: Boolean = true,
path: String,
soundFile: SoundFile?,
waveDataMap: WaveDataMap?
) {
if (isFinishCallBack) {
ampTaskMap[path]?.forEach { ampTaskData ->
ampTaskData.apply {
if (typeOrSegments.isNullOrEmpty()) {
DebugUtil.e(TAG, "dispatchFinishCallBack typeOrSegments is null !")
} else {
ampFinishCallBack?.invoke(waveDataMap?.getAmpByTypeOrSegments(typeOrSegments))
}
}
}
ampTaskMap.remove(path)
} else {
ampTaskMap[path]?.forEach { ampTaskData ->
ampTaskData.apply {
if (typeOrSegments.isNullOrEmpty()) {
DebugUtil.e(TAG, "dispatchReadyCallBack typeOrSegments is null !")
} else {
ampReadyCallBack?.invoke(soundFile?.amplitudeList)
}
}
}
}
}
/**
* getMarkStringByPath
*/
fun getMarkStringByPath(
path: String?
): String? {
if (path.isNullOrEmpty()) {
DebugUtil.e(TAG, "getMarkStringByPath path is null !")
return null
}
return ampTaskMap.getAmplitudeListUtilByPath(path)?.markString
}
/**
* getAmpFormDbOrCustomTagByPath (Initialization is required -> initAmplitudeListUtil())
*/
fun getAmpFormDbOrCustomTagByPath(
path: String?,
ampTypeOrSegments: String
): List<Int>? {
if (path.isNullOrEmpty()) {
DebugUtil.e(TAG, "getAmpFormDbOrCustomTagByPath path is null !")
return null
}
return ampTaskMap.getAmplitudeListUtilByPath(path)?.ampList?.getAmpByTypeOrSegments(ampTypeOrSegments)
}
fun releaseMp3ByPath(
path: String?
) {
if (path.isNullOrEmpty()) {
DebugUtil.e(TAG, "releaseMp3ByPath path is null !")
return
}
ampTaskMap.getAmplitudeListUtilByPath(path)?.releaseMp3()
}
fun genAmpFromSoundFileAndSetFunc(
path: String?,
playUri: Uri?,
isRecycle: Boolean,
decodeReadyCallback: (List<Int>) -> Unit,
decodeFinishCallback: (String) -> Unit
) {
if (path.isNullOrEmpty() || playUri == null) {
DebugUtil.e(TAG, "genAmpFromSoundFileAndSetFunc path:$path or playUri:$playUri is null !")
return
}
ampTaskMap.getAmplitudeListUtilByPath(path)?.apply {
val isExistTask = addGetAmplitudeTask(
path, playUri, isRecycle, WaveConstant.FULL_AMP_TYPE,
{ decodeReadyCallback } , { decodeFinishCallback }
)
decodeFile(this@apply, path, playUri)
/*if (isExistTask.not()) {
decodeFile(this@apply, path, playUri)
} else {
DebugUtil.d(TAG, "genAmpFromSoundFileAndSetFunc exist task path:$path")
}*/
/* {
setDecodeReady { _, soundFile ->
decodeReadyCallback.invoke(soundFile.amplitudeList)
}
}, {
setDecodeFinish { ampString ->
decodeFinishCallback.invoke(ampString)
}
}*/
}
}
fun releaseSoundByPath(
path: String?
) {
if (path.isNullOrEmpty()) {
DebugUtil.e(TAG, "releaseSoundByPath path is null !")
return
}
ampTaskMap.getAmplitudeListUtilByPath(path)?.releaseSound()
}
fun releaseAmplitudeListUtilByPath(
path: String?
) {
if (path.isNullOrEmpty()) {
DebugUtil.e(TAG, "genAmpFromSoundFileAndSetFun path is null !")
return
}
ampTaskMap.getAmplitudeListUtilByPath(path)?.apply {
release()
remove(path)
}
}
fun addGetAmplitudeTask(
path: String?,
playUri: Uri?,
isRecycle: Boolean,
ampTypeOrSegments: String,
decodeReadyCallback: ((waveDataList: List<Int>?) -> Unit)? = null,
decodeFinishCallback: ((fullWaveData: String?) -> Unit)? = null
) : Boolean {
if (path.isNullOrEmpty()|| playUri == null) {
DebugUtil.e(TAG, "addGetAmplitudeTask path:$path or playUri:$playUri is null !")
return false
}
if (ampTaskMap.containsKey(path)) {
DebugUtil.d(TAG, "getAmplitudeByType ampTaskMap.containsKey(path) path:$path")
val amplitudeListUtil = ampTaskMap.getAmplitudeListUtilByPath(path)
ampTaskMap[path]?.apply {
add(AmpTaskData(amplitudeListUtil,
{ decodeReadyCallback
DebugUtil.d(TAG,"xak2 amplitudeListUtil decode READY")}, { decodeFinishCallback }, ampTypeOrSegments))
}
return true
} else {
val amplitudeListUtil = AmplitudeListUtil(
BaseApplication.getAppContext(),
path,
playUri,
isRecycle
)
ampTaskMap.put(
path, arrayListOf(
AmpTaskData(
amplitudeListUtil, { decodeReadyCallback },
{ decodeFinishCallback }, ampTypeOrSegments
)
)
)
return false
}
}
fun HashMap<String, ArrayList<AmpTaskData>>.getAmplitudeListUtilByPath(path: String?): AmplitudeListUtil? {
if (path.isNullOrEmpty()) {
DebugUtil.e(TAG, "getAmplitudeListUtilByPath path is null !")
return null
}
return this.get(path)?.get(0)?.amplitudeListUtil
}
}
data class AmpTaskData(
val amplitudeListUtil: AmplitudeListUtil? = null,
val ampReadyCallBack: ((waveReadyDataList: List<Int>?) -> Unit)? = null,
val ampFinishCallBack: ((waveDataList: List<Int>?) -> Unit)? = null,
val typeOrSegments: String?
)
DebugUtil.d(TAG,"xak2 amplitudeListUtil decode READY") 这个打印有,但是前面的decodeReadyCallback没执行呢?