我正在编写一个应用程序来播放远程服务器的音频。我尝试了多种方法来实现流音频,但它们对我来说都不够好。
这就是我尝试过的:
幼稚地使用 MediaPlayer
就像是:
MediaPlayer player = new MediaPlayer();
player.setDataSource(context, Uri.parse("http://whatever.com/track.mp3"));
player.prepare();
player.start();
(or prepareAsync
, 不管)
但标准MediaPlayer
播放远程内容时相当不稳定。它经常掉落或停止播放,我无法处理这个问题。另一方面,我想实现媒体缓存。但我还没有找到任何方法从 MediaPlayer 获取缓冲内容并将其保存在设备上的某个位置。
实现自定义缓冲
然后就产生了一种想法,即按块下载媒体文件,将它们组合成一个本地文件并播放该文件。由于连接不良,下载整个文件可能会很慢,因此最好先下载足够的初始片段,然后开始播放并继续下载并附加本地文件。此外,我们还获得了缓存功能。
听起来像是一个计划,但它并不总是有效。它在 HTC Sensation XE 上完美运行,但在完成这首曲子后,在 4.1 平板电脑上播放并没有停止。不知道,为什么会这样。我问过question https://stackoverflow.com/questions/16273133/play-media-file-and-append-it-in-the-same-time对此,但没有得到答复。
使用两个媒体播放器
我创建了两个 MediaPlayer 实例并尝试使它们相互更改。逻辑如下:
- 开始下载初始媒体片段
- 下载后,通过 currentMediaPlayer 开始播放。其余媒体继续报道
下载
- 当下载的片段即将播放时(完成前 1 秒),使用相同的源文件准备 secondaryMediaPlayer(因为它是在播放过程中附加的)
- currentMediaPlayer 完成前 261 毫秒 – 暂停它,启动辅助播放器,将辅助播放器设置为当前播放器,安排下一个辅助播放器的准备。
来源:
private static final String FILE_NAME="local.mp3";
private static final String URL = ...;
private static final long FILE_SIZE = 7084032;
private static final long PREPARE_NEXT_PLAYER_OFFSET = 1000;
private static final int START_NEXT_OFFSET = 261;
private static final int INIT_PERCENTAGE = 3;
private MediaPlayer mPlayer;
private MediaPlayer mSecondaryPlayer;
private Handler mHandler = new Handler();
public void startDownload() {
mDownloader = new Mp3Downloader(FILE_NAME, URL, getExternalCacheDir());
mDownloader.setDownloadListener(mInitDownloadListener);
mDownloader.startDownload();
}
private Mp3Downloader.DownloadListener mInitDownloadListener = new Mp3Downloader.DownloadListener() {
public void onDownloaded(long bytes) {
int percentage = Math.round(bytes * 100f / FILE_SIZE);
// Start playback when appropriate piece of media downloaded
if (percentage >= INIT_PERCENTAGE) {
mPlayer = new MediaPlayer();
try {
mPlayer.setDataSource(mDownloader.getDownloadingFile().getAbsolutePath());
mPlayer.prepare();
mPlayer.start();
mHandler.postDelayed(prepareSecondaryPlayerRunnable, mPlayer.getDuration() - PREPARE_NEXT_PLAYER_OFFSET);
mHandler.postDelayed(startNextPlayerRunnable, mPlayer.getDuration() - START_NEXT_OFFSET);
} catch (IOException e) {
Log.e(e);
}
mDownloader.setDownloadListener(null);
}
}
};
// Starting to prepare secondary MediaPlayer
private Runnable prepareSecondaryPlayerRunnable = new Runnable() {
public void run() {
mSecondaryPlayer = new MediaPlayer();
try {
mSecondaryPlayer.setDataSource(mDownloader.getDownloadingFile().getAbsolutePath());
mSecondaryPlayer.prepare();
mSecondaryPlayer.seekTo(mPlayer.getDuration() - START_NEXT_OFFSET);
} catch (IOException e) {
Log.e(e);
}
}
};
// Starting secondary MediaPlayer playback, scheduling creating next MediaPlayer
private Runnable startNextPlayerRunnable = new Runnable() {
public void run() {
mSecondaryPlayer.start();
mHandler.postDelayed(prepareSecondaryPlayerRunnable, mSecondaryPlayer.getDuration() - mPlayer.getCurrentPosition() - PREPARE_NEXT_PLAYER_OFFSET);
mHandler.postDelayed(startNextPlayerRunnable, mSecondaryPlayer.getDuration() - mPlayer.getCurrentPosition() - START_NEXT_OFFSET);
mPlayer.pause();
mPlayer.release();
mPlayer = mSecondaryPlayer;
}
};
再说一遍——听起来像是一个计划,但效果并不完美。切换媒体播放器的时刻非常清晰。这里我遇到相反的情况:在 4.1 平板电脑上没问题,但在 HTC Sensation 上有明显的滞后。
我还尝试实现不同的下载技术。我已经实现了按 10Kb 块和 MP3 帧进行下载。我不太清楚,但似乎在 MP3 帧的情况下,seekTo 和 start 工作得更好。但这只是一种感觉,我不知道解释。
流媒体播放器
我在谷歌搜索时多次看到这个词,并找到了这个实现:https://code.google.com/p/mynpr/source/browse/trunk/mynpr/src/com/webeclubbin/mynpr/StreamingMediaPlayer.java?r=18 https://code.google.com/p/mynpr/source/browse/trunk/mynpr/src/com/webeclubbin/mynpr/StreamingMediaPlayer.java?r=18
这是每个人都使用的解决方案吗?
如果是的话,那就很难过,因为它对我来说也没有好处。我在实施中没有看到任何新的想法。
所以,问题
你们如何在应用程序中实现音频流?我不相信我是唯一遇到此类问题的人。应该有一些好的做法。