[taxi][passenger]
[音乐播放]
This commit is contained in:
yangyakun
2024-03-06 19:25:52 +08:00
parent 29913b6eee
commit 6d2deed800
36 changed files with 1842 additions and 250 deletions

View File

@@ -0,0 +1,155 @@
package com.mogo.och.common.module.manager.auditionmanager
import android.media.AudioManager
import android.media.MediaPlayer
import android.os.Handler
import android.os.HandlerThread
import android.os.Message
import android.text.TextUtils
//播放试听
object Audition: MediaPlayer.OnPreparedListener,
MediaPlayer.OnCompletionListener, MediaPlayer.OnSeekCompleteListener {
private val TAG = "Audition"
var mediaPlayer: MediaPlayer? = null
var oldPath: String? = null
private var listener: OnAuditionListener? = null
private val handler: Handler
interface OnAuditionListener {
fun onAuditionCompletion(path: String?)
fun onSeekCompletion(currentPlay: Long)
fun onCurrentPosition(currentPlay: Long, duration: Long)
}
fun registerOnAuditionListener(listener: OnAuditionListener?) {
this.listener = listener
}
fun unregisterOnAuditionListener() {
listener = null
}
init {
val frequentThread = HandlerThread("frequent_drawer")
frequentThread.start()
handler = object : Handler(frequentThread.looper) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if (msg.what == 0) {
if (mediaPlayer != null && listener != null) {
val currentPosition = mediaPlayer!!.currentPosition
val duration = mediaPlayer!!.duration
listener!!.onCurrentPosition(currentPosition.toLong(), duration.toLong())
}
sendEmptyMessageDelayed(0, 500)
}
}
}
}
val isPlaying: Boolean
get() = mediaPlayer != null && mediaPlayer!!.isPlaying
fun play(path: String) {
if (TextUtils.equals(oldPath, path)) {
return
}
oldPath = path
if (mediaPlayer != null) {
mediaPlayer!!.release()
mediaPlayer = null
}
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer()
mediaPlayer!!.setAudioStreamType(AudioManager.STREAM_MUSIC)
mediaPlayer!!.setOnPreparedListener(this)
mediaPlayer!!.setOnCompletionListener(this)
mediaPlayer!!.setOnSeekCompleteListener(this)
}
try {
mediaPlayer!!.setDataSource(path)
} catch (e: Exception) {
e.printStackTrace()
}
mediaPlayer!!.prepareAsync()
}
fun stop() {
oldPath = null
if (mediaPlayer != null) {
mediaPlayer!!.stop()
mediaPlayer!!.reset()
}
}
fun pause() {
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
mediaPlayer!!.pause()
}
}
fun toggle(path: String):Boolean {
if (mediaPlayer != null) {
if(TextUtils.equals(oldPath, path)) {
if (mediaPlayer!!.isPlaying) {
mediaPlayer!!.pause()
return false
} else {
mediaPlayer!!.start()
return true
}
}else{
play(path)
return true
}
}else{
play(path)
return true
}
}
fun start():Boolean {
if (mediaPlayer != null) {
mediaPlayer!!.start()
return true
}
return false
}
fun playOrStop(path: String): Boolean {
return if (!TextUtils.equals(oldPath, path)) {
play(path)
true
} else {
stop()
false
}
}
fun onDestroy() {
stop()
mediaPlayer = null
}
override fun onPrepared(mp: MediaPlayer) {
mp.start()
if (listener != null) {
listener!!.onCurrentPosition(mp.currentPosition.toLong(), mp.duration.toLong())
}
handler.sendEmptyMessageDelayed(0, 500)
}
override fun onCompletion(mp: MediaPlayer) {
if (listener != null) {
listener!!.onAuditionCompletion(oldPath)
}
oldPath = null
}
override fun onSeekComplete(mp: MediaPlayer) {
if (listener != null) {
listener!!.onSeekCompletion(mp.currentPosition.toLong())
}
}
}

View File

@@ -0,0 +1,257 @@
package com.mogo.och.common.module.manager.auditionmanager
import android.media.MediaExtractor
import android.media.MediaFormat
import android.net.Uri
import android.os.Environment
import android.text.TextUtils
import com.google.gson.reflect.TypeToken
import com.mogo.commons.AbsMogoApplication
import com.mogo.eagle.core.data.config.FunctionBuildConfig
import com.mogo.eagle.core.utilcode.download.DownloadUtils
import com.mogo.eagle.core.utilcode.download.callback.IDownloadListener
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_OCHCOMMON
import com.mogo.eagle.core.utilcode.util.GsonUtils
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import com.mogo.eagle.core.utilcode.util.Utils
import com.mogo.och.common.module.manager.orderlogmanager.OchChainLogManager
import com.mogo.och.common.module.manager.orderlogmanager.OchChainLogManager.EVENT_KEY_INFE_WITH_MUSIC
import com.mogo.och.common.module.utils.FileUtils
import com.mogo.och.common.module.wigets.media.MediaPlayLogger
import rx.Single
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import com.mogo.eagle.core.utilcode.util.FileUtils as FileHelper
object AuditionCacheManager {
private var dataChangeListener:DataChangeListener?=null
private val context = AbsMogoApplication.getApp()
private const val TAG = "AuditionCacheManager"
init {
createFileCacheDir()
}
// 命名规则 缓存文件夹/md5.后缀名
private fun getMusicDataByLocationConfig() {
ThreadUtils.getIoPool().execute {
val localAdDataList = mutableListOf<MusicData>()
try {
val datas: MusicDataList = GsonUtils.fromJson(
FunctionBuildConfig.musicUrlConfig, object : TypeToken<MusicDataList>() {}.type
)
localAdDataList.addAll(datas.musics)
} catch (e: Exception) {
e.printStackTrace()
}
localAdDataList.forEach {
if (isLocalCacheFileExists(it)) {
// 存在
val locationPath = getCacheFileFullPathByUrl(it)
val fileMD5 = FileHelper.getFileMD5ToString(locationPath)
if(fileMD5==it.md5){// 文件存在切md5一致
it.path = locationPath
val duration = getDuration(locationPath)
it.duration = duration
dataChangeListener?.addOneData(it)
CallerLogger.d(M_OCHCOMMON+ TAG,"文件已下载添加播放列表中")
addTag("文件已下载添加播放列表中:${it}")
}else{
// 删除缓存
FileHelper.delete(locationPath)
if(it.isCloud()) {
// 去下载
downloadFile(it)
addTag("缓存已下载但md5对应不上:${it}")
}else{
copyRaw2Music(it.songUrl,locationPath)
it.path = locationPath
val duration = getDuration(locationPath)
it.duration = duration
dataChangeListener?.addOneData(it)
}
}
}else{
if(it.isCloud()) {
// 不存在
downloadFile(it)
addTag("第一次下载:${it}")
}else{
val locationPath = getCacheFileFullPathByUrl(it)
copyRaw2Music(it.songUrl,locationPath)
it.path = locationPath
val duration = getDuration(locationPath)
it.duration = duration
dataChangeListener?.addOneData(it)
}
}
}
}
}
/**
* 创建media缓存文件夹目录优先放SD卡
*/
private fun createFileCacheDir(): Boolean {
val cacheDirPath = getFileCacheDir()
MediaPlayLogger.printInfoLog("createFileCacheDir dirPath=$cacheDirPath")
return FileHelper.createFileDir(cacheDirPath)
}
/**
* 获取本地缓存文件的文件全路径
*/
private fun getFileCacheDir(): String {
// 有些手机需要通过自定义目录
val relativePath = "mogo" + File.separator + "music" + File.separator
val cacheDir = File(Environment.getExternalStorageDirectory(), relativePath)
if (FileHelper.createOrExistsDir(cacheDir)) {
return cacheDir.absolutePath
}
return FileUtils.getCacheDirectory(context, "") + relativePath
}
/**
* 获取文件缓存的缓存path, 文件名以base64编码避免 中文命名,重复文件名的影响
*/
private fun getCacheFileFullPathByUrl(musicData: MusicData): String {
return getFileCacheDir() +File.separator+ getCacheFileName(musicData)
}
/**
* 本地是否已经存在下载完成的文件
*/
private fun isLocalCacheFileExists(musicData: MusicData): Boolean {
val localVideoCacheFilePath = getCacheFileFullPathByUrl(musicData)
return FileHelper.isFileExists(localVideoCacheFilePath)
}
/**
* 本地缓存文件的文件名md5编码避免文件名重复或者特殊字符编码问题
*/
private fun getCacheFileName(musicData: MusicData): String {
val fileSuffix = FileUtils.getExtension(musicData.songUrl)
if (TextUtils.isEmpty(fileSuffix)) {
return ""
}
return musicData.md5 + FileUtils.EXTENSION_SEPARATOR + fileSuffix
}
/**
* 下载文件
*/
private fun downloadFile(musicData: MusicData) {
val downloadUrl = musicData.songUrl
val downloadDir = getFileCacheDir()
val downloadFileName = getCacheFileName(musicData)
DownloadUtils.downLoad(context, downloadUrl, downloadDir, downloadFileName, object :IDownloadListener{
override fun onStart(url: String) {
addTag("开始下载:${musicData}")
}
override fun onProgress(url: String, downloaded: Long, total: Long) {
}
override fun onFinished(url: String, path: String) {
val fileMD5 = FileHelper.getFileMD5ToString(path)
if(fileMD5==musicData.md5){// 文件存在切md5一致
musicData.path = path
dataChangeListener?.addOneData(musicData)
addTag("下载完成切md5对应上:${musicData}")
}else{
// 下载成功但是下载的文件md5不一致不能使用
}
}
override fun onError(url: String, error: String?) {
// 下载失败
addTag("下载失败:${musicData}")
}
})
}
fun addDataChangeListener(dataChangeListener: DataChangeListener) {
this.dataChangeListener = dataChangeListener
getMusicDataByLocationConfig()
}
public interface DataChangeListener{
fun addOneData(it: MusicData)
}
private fun addTag(logInfo:String){
OchChainLogManager.writeChainLog(TAG,logInfo,eventID=EVENT_KEY_INFE_WITH_MUSIC)
}
/**
* 获取mp3音频的总时长 单位ms
*
* @param mp3FilePath MP3文件路径
* @return 时长
*/
fun getDuration(mp3FilePath: String): Long {
if (!com.mogo.eagle.core.utilcode.util.FileUtils.isFileExists(mp3FilePath)) {
return 0
}
if (!mp3FilePath.endsWith("mp3")) {
return 0
}
var mex: MediaExtractor? = null
try {
mex = MediaExtractor()
mex.setDataSource(mp3FilePath)
val mf = mex.getTrackFormat(0)
return mf.getLong(MediaFormat.KEY_DURATION) / 1000L
} catch (e: IOException) {
} finally {
mex?.release()
}
return 0
}
fun copyRaw2Music(url:String,dst:String){
val path = Uri.parse(url).path
path?.let {
val split = it.split("/")
if(split.size==3&&split.get(1)=="raw"){
try {
val fileName = split[2]
val nameAndprex = fileName.split(".")
val resourceId: Int = context.resources.getIdentifier(nameAndprex[0], "raw", context.packageName)
val openRawResource = context.resources.openRawResource(resourceId)
val outputStream: OutputStream = FileOutputStream(dst)
val buffer = ByteArray(1024)
var length: Int
while (openRawResource.read(buffer).also { length = it } > 0) {
outputStream.write(buffer, 0, length)
}
outputStream.close()
openRawResource.close()
}catch (e:IOException){
e.printStackTrace()
}
}
}
}
}

View File

@@ -0,0 +1,156 @@
package com.mogo.och.common.module.manager.auditionmanager
import com.mogo.och.common.module.manager.distancemamager.IDistanceListener
import com.mogo.och.common.module.manager.distancemamager.TrajectoryAndDistanceManager
import java.util.concurrent.ConcurrentHashMap
object AuditionManager: AuditionCacheManager.DataChangeListener, Audition.OnAuditionListener {
val musicList = mutableListOf<MusicData>()
private val dataChangeListeners: ConcurrentHashMap<String, MusicDataChangeListener> = ConcurrentHashMap()
init {
AuditionCacheManager.addDataChangeListener(this)
Audition.registerOnAuditionListener(this)
}
fun addDataChangeListener(tag: String, listener: MusicDataChangeListener) {
if (dataChangeListeners.containsKey(tag)) {
return
}
dataChangeListeners[tag] = listener
}
fun removeDataChangeListener(tag: String){
dataChangeListeners.remove(tag)
}
private fun getMusicDataByState(state:PlayState):Pair<Int,MusicData>?{
musicList.forEachIndexed { index, musicData ->
if(musicData.state==state){
return Pair(index,musicData)
}
}
return null
}
fun toggle(musicData: MusicData){
val toggle = Audition.toggle(musicData.path)
val oldData = resetData()
if(toggle){// 播放
musicData.state = PlayState.Playing
dataChangeListeners.forEach {
it.value.updateState(oldData,musicData)
}
}else{// 暂停
musicData.state = PlayState.Pause
dataChangeListeners.forEach {
it.value.updateState(oldData,musicData)
}
}
}
private fun resetData():MusicData?{
var tempRusult:MusicData?=null
musicList.forEach {
if(it.state!=PlayState.None){
tempRusult = it
}
it.state = PlayState.None
}
return tempRusult
}
override fun addOneData(musicDataNew: MusicData) {
musicList.forEachIndexed { index, musicData ->
if (musicDataNew.id==musicData.id) {
musicList[index] = musicDataNew
return
}
}
musicList.add(musicDataNew)
dataChangeListeners.forEach {
it.value.addOneData(musicDataNew)
}
}
public interface MusicDataChangeListener{
fun addOneData(it: MusicData){}
fun updateState(oldData: MusicData?,musicData: MusicData)
fun updatePlayCurrent(currentPlay: Long, duration: Long, second: MusicData){}
fun onMusicCompletion(musicData: MusicData) {}
}
/**
* 音乐播放完毕
*/
override fun onAuditionCompletion(path:String?) {
val oldData = resetData()
if(oldData!=null&&oldData.path==path){
dataChangeListeners.forEach {
it.value.onMusicCompletion(oldData)
}
}else{
musicList.forEach {musicData->
if(musicData.path==path){
dataChangeListeners.forEach {
it.value.onMusicCompletion(musicData)
}
return
}
}
}
}
/**
* 拖动跳转完成
*/
override fun onSeekCompletion(currentPlay: Long) {
}
/**
* 当前进度
*/
override fun onCurrentPosition(currentPlay: Long, duration: Long) {
val musicDataWithIndex = getMusicDataByState(PlayState.Playing)
if (musicDataWithIndex!=null) {
musicDataWithIndex.second.let {musicData->
dataChangeListeners.forEach {
it.value.updatePlayCurrent(currentPlay,duration,musicData)
}
}
}else{
musicList.forEachIndexed { index, musicData ->
if(musicData.path==Audition.oldPath){
dataChangeListeners.forEach {
it.value.updatePlayCurrent(currentPlay,duration,musicData)
}
return
}
}
}
}
fun getNextMusicData(it: MusicData):MusicData {
val indexOf = musicList.indexOf(it)
if(indexOf== musicList.size-1){
return musicList.first()
}else{
return musicList[indexOf+1]
}
}
fun getPreMusicData(it: MusicData): MusicData {
val indexOf = musicList.indexOf(it)
if(indexOf== 0){
return musicList.last()
}else{
return musicList[indexOf-1]
}
}
}

View File

@@ -0,0 +1,36 @@
package com.mogo.och.common.module.manager.auditionmanager
data class MusicDataList(val musics: MutableList<MusicData>)
/**
* 音乐文件
*/
data class MusicData(
val id: String,
val md5:String,
val songName: String,
val songUrl: String,
val songUrlType: String,
val coverHeadImageUrl: String,
val coverBottomImageUrl: String,
val tag: MutableList<String>,
var duration:Long,
var path: String,
var state: PlayState = PlayState.None
){
fun isCloud():Boolean{
return songUrlType=="cloud"
}
}
/**
* None -> Playing - Pause
* | |
* None None
*/
enum class PlayState {
None,
Playing,
Pause,
}

View File

@@ -17,6 +17,7 @@ object OchChainLogManager {
const val EVENT_KEY_INFE_WITH_CHANGE = "event_key_och_common_info_and_changeinfo"
const val EVENT_KEY_INFE_WITH_TRAJECTORY = "event_key_och_trajectory_info"
const val EVENT_KEY_INFE_WITH_MUSIC = "event_key_och_music_info"
/**
* @param Info 订单详细信息