Android - 从byte[]播放mp3

41

我有一个以byte[]格式的mp3文件(从服务中下载),我希望像播放其他文件一样在我的设备上播放它:

MediaPlayer mp = new MediaPlayer();
mp.setDataSource(PATH_TO_FILE);
mp.prepare();
mp.start();

但我似乎找不到方法。我不介意把文件保存到手机上,然后再播放它。如何播放该文件,或者下载后再播放它?


如果有人遇到媒体音量与铃声音量问题(声音“似乎在播放”,但您听不到任何声音),请查看此页面:https://dev59.com/0HRB5IYBdhLWcg3wa2q2 - nikib3ro
6个回答

79

好的,感谢大家的回答,但我需要从byte[]中播放mp3文件,因为我是从.NET webservice获取的(不希望在服务器上存储动态生成的mp3文件)。

最终,要播放简单的mp3有许多注意事项...这里是代码,供需要的人使用:

private MediaPlayer mediaPlayer = new MediaPlayer();
private void playMp3(byte[] mp3SoundByteArray) {
    try {
        // create temp file that will hold byte array
        File tempMp3 = File.createTempFile("kurchina", "mp3", getCacheDir());
        tempMp3.deleteOnExit();
        FileOutputStream fos = new FileOutputStream(tempMp3);
        fos.write(mp3SoundByteArray);
        fos.close();

        // resetting mediaplayer instance to evade problems
        mediaPlayer.reset();

        // In case you run into issues with threading consider new instance like:
        // MediaPlayer mediaPlayer = new MediaPlayer();                     

        // Tried passing path directly, but kept getting 
        // "Prepare failed.: status=0x1"
        // so using file descriptor instead
        FileInputStream fis = new FileInputStream(tempMp3);
        mediaPlayer.setDataSource(fis.getFD());

        mediaPlayer.prepare();
        mediaPlayer.start();
    } catch (IOException ex) {
        String s = ex.toString();
        ex.printStackTrace();
    }
}

编辑:我写下这个答案已经超过4年了 - 很明显,自那时以来很多事情都发生了变化。请参阅Justin的评论以了解如何重用MediaPlayer实例。另外,我不知道.deleteOnExit()现在是否适用于您 - 请随意建议改进措施,以免临时文件堆积。


2
它对我不起作用,它提供了一个错误01-27 15:39:44.686:W/System.err(1022):java.io.IOException:setDataSourceFD失败:status=0x80000000 - Hemant Metalia
7
如果你这样重复使用 MediaPlayer,如果调用代码的次数太多,最终会耗尽资源。你应该使用一个单一的 MediaPlayer 实例,但在调用 mediaPlayer.setDataResource(..) 之前调用 mediaPlayer.reset();这样你就不会崩溃,也不会耗尽资源。 - Justin
@Justin 谢谢分享... 幸运的是,自 Android 1.5 以来有很多变化 ;) - nikib3ro
1
@kape123 能否详细说明一下? - Justin
临时文件根本没有被删除。我的缓存中有越来越多的文件。有什么想法吗? - Bagusflyer
显示剩余2条评论

25

我通过将我的MP3文件编码为Base64(因为我已经从Restful API服务接收到编码后的数据),然后创建一个URL对象,找到了一个简单的解决方案。 我在Android 4.1中进行了测试。

public void PlayAudio(String base64EncodedString){
        try
        {
            String url = "data:audio/mp3;base64,"+base64EncodedString; 
            MediaPlayer mediaPlayer = new MediaPlayer();
            mediaPlayer.setDataSource(url);
            mediaPlayer.prepare();
            mediaPlayer.start();
        }
        catch(Exception ex){
            System.out.print(ex.getMessage());
        }
    }

1
浏览器对数据URL有大小限制。Android是否强制实施任何大小限制? - ccpizza
太棒了!在Android 10上运行。 - Alexander L.

17

从Android MarshMellow (版本代码23)开始,有一种新的API可以实现这一点。

MediaPlayer.setDataSource(android.media.MediaDataSource)

您可以提供MediaDataSource的自定义实现并包装一个byte[]。下面是一个基本的实现。

import android.annotation.TargetApi;
import android.media.MediaDataSource;
import android.os.Build;
import java.io.IOException;


@TargetApi(Build.VERSION_CODES.M)
public class ByteArrayMediaDataSource extends MediaDataSource {

    private final byte[] data;

    public ByteArrayMediaDataSource(byte []data) {
        assert data != null;
        this.data = data;
    }
    @Override
    public int readAt(long position, byte[] buffer, int offset, int size) throws IOException {
        System.arraycopy(data, (int)position, buffer, offset, size);
        return size;
    }

    @Override
    public long getSize() throws IOException {
        return data.length;
    }

    @Override
    public void close() throws IOException {
        // Nothing to do here
    }
}

5

如果你对字节数组/字节流不确定,但是如果你有一个来自服务的URL,你可以尝试通过调用将数据源设置为网络URI。

setDataSource(Context context, Uri uri)

请见API文档

在处理媒体时,始终遵循API是明智的选择,除非您绝对确定自己的方法更好/可以克服其实现中的一些明显缺陷。在这种情况下,您应该提交错误报告。 - Sneakyness
如果我们将像http://server/song.mp3这样的URI传递给播放器,它会初始化吗? - Bohemian
所以我正在检查这个。我将歌曲位置作为Uri传递给播放器。它可以正常播放。 没有文件处理。 我正在2.01 /模拟器上测试它。 - Bohemian
有人尝试过吗: Uri uri = Uri.fromFile(file); 甚至Android文档中给出了一个可能被传递的文件类型的示例: 例如:"file:///tmp/android.txt" - JamisonMan111

3
如果您的目标API是23或更高版本,请创建一个类,类似于以下内容。
class MyMediaDataSource(val data: ByteArray) : MediaDataSource() {

override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int {

    if (position >= data.size) return -1 // -1 indicates EOF 

    val endPosition: Int = (position + size).toInt()
    var size2: Int = size
    if (endPosition > data.size)
        size2 -= endPosition - data.size

    System.arraycopy(data, position.toInt(), buffer, offset, size2)
    return size2
}

override fun getSize(): Long {
    return data.size.toLong()
}

override fun close() {}

}

并且像这样使用

val mediaSource = MyMediaDataSource(byteArray)
MediaPlayer().apply {
    setAudioStreamType(AudioManager.STREAM_MUSIC)
    setDataSource(mediaSource)
    setOnCompletionListener { release() }
    prepareAsync()
    setOnPreparedListener { start() }
}

感谢krishnakumarp上面的回答以及这篇文章


这对我有用,尽管我不得不使用 setAudioAttributes(AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED).setLegacyStreamType(AudioManager.STREAM_MUSIC).setUsage(AudioAttributes.USAGE_GAME).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build()) 而不是现在已弃用的 setAudioStreamType(...) - user1300214

0
错误的代码:
 MediaPlayer mp = new MediaPlayer();
 mp.setDataSource(PATH_TO_FILE);
 mp.prepare();
 mp.start();

正确代码:

 MediaPlayer mp = new MediaPlayer();
 mp.setDataSource(PATH_TO_FILE);
 mp.setOnpreparedListener(this);
 mp.prepare();

//Implement OnPreparedListener 
OnPrepared() {
    mp.start();
 }

查看 API 演示...


1
如果你是正确的,那么看起来他们需要更新文档 -> http://developer.android.com/guide/topics/media/index.html#playfile - nikib3ro
3
根据文档,prepare()方法是同步的。只有当您调用prepareAsync()方法时,才需要使用监听器。然而,如果您的数据源是一个流,则应该调用prepareAsync()方法,该方法会立即返回,而不是阻塞直到缓冲区中有足够的数据为止。 - Jerry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接