[6.3.0]
[taxi][passenger] [音乐播放]
This commit is contained in:
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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 订单详细信息
|
||||
|
||||
Reference in New Issue
Block a user