我一直在尝试使用prepareAsync()方法和onPrepared()方法来实现一个简单的MediaPlayer(音频)。这些是在IntentService中调用的。相关 Activity 仅发送开始和停止意图 Action 。我已验证事件是从 Activity 发送的。
这是IntentService中的代码:
package com.javacodegeeks.androidmusic_intentservice;
import android.app.IntentService;
import android.content.Intent;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.widget.Toast;
import java.io.IOException;
import android.net.Uri;
/**
* An {@link IntentService} subclass for handling asynchronous task requests in
* a service on a separate handler thread.
* <p/>
* TODO: Customize class - update intent actions, extra parameters and static
* helper methods.
*/
public class MusicIntentService extends IntentService implements MediaPlayer.OnErrorListener, MediaPlayer.OnPreparedListener {
// TODO: Rename actions, choose action names that describe tasks that this
private static final String ACTION_PLAY = "com.example.action.PLAY";
private static final String ACTION_STOP = "com.example.action.STOP";
static MediaPlayer mMediaPlayer = null;
public MusicIntentService() {
super("MusicIntentService");
}
@Override
public void onHandleIntent(Intent intent) {
if (intent != null) if (intent.getAction().equals(ACTION_PLAY)) {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
String fileName = "android.resource://" + getPackageName() + "/" + R.raw.georgeharrisonlivinginthematerialworld;
try {
mMediaPlayer.setDataSource(this, Uri.parse(fileName));
} catch (IllegalArgumentException e) {
Toast.makeText(getApplicationContext(), "IllegalArgumentException", Toast.LENGTH_LONG).show();
} catch (SecurityException e) {
Toast.makeText(getApplicationContext(), "SecurityException", Toast.LENGTH_LONG).show();
} catch (IllegalStateException e) {
Toast.makeText(getApplicationContext(), "IllegalStateException", Toast.LENGTH_LONG).show();
} catch (IOException e) {
e.printStackTrace();
}
}
mMediaPlayer.setOnErrorListener(this);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setOnPreparedListener(this);
try {
mMediaPlayer.prepareAsync();
} catch (IllegalStateException e) {
Toast.makeText(getApplicationContext(), "IllegalStateException", Toast.LENGTH_LONG).show();
}
/*calling prepare() and start() works ok*/
/* it is better to have the prepareAsync() and onPrepared() pattern implemented*/
/* try {
mMediaPlayer.prepare();
} catch (IllegalStateException e) {
Toast.makeText(getApplicationContext(), "IllegalStateException prepare()", Toast.LENGTH_LONG).show();
} catch (IOException e) {
Toast.makeText(getApplicationContext(), "IOException prepare()", Toast.LENGTH_LONG).show();
}*/
/* try {
mMediaPlayer.start();
} catch (IllegalStateException e) {
Toast.makeText(getApplicationContext(), "IllegalStateException start()", Toast.LENGTH_LONG).show();
}*/
} else if (intent.getAction().equals(ACTION_STOP)) {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
}
}
}
/** Called when MediaPlayer is ready */
public void onPrepared(MediaPlayer player) {
try {
mMediaPlayer.start();
} catch (IllegalStateException e) {
Toast.makeText(getApplicationContext(), "IllegalStateException in onPrepared", Toast.LENGTH_LONG).show();
}
}
public boolean onError(MediaPlayer mp, int what, int extra) {
/*
... react appropriately ...
The MediaPlayer has moved to the Error state, must be reset!
*/
return true;
}
}
我知道控制逻辑不是安全的,但我什至无法在第一个Play命令上达到onPrepared。当我通过运行或调试(模拟器是Nexus 5 API 21 x86,在MacBook Pro上运行,并使用Android Studio用API21编译)执行代码时,永远无法达到onPrepared()函数。尝试在Samsung SM-G900T(API 19)上运行也是如此。
我在Logcat窗口中收到以下日志,日志级别为Debug,并过滤掉与此软件包无关的所有内容:
01-07 02:29:04.203 2208-2208/com.javacodegeeks.androidmusic_intentservice I/System.out﹕ waiting for debugger to settle...
01-07 02:29:04.414 2208-2208/com.javacodegeeks.androidmusic_intentservice I/System.out﹕ debugger has settled (1484)
01-07 02:29:04.521 2208-2235/com.javacodegeeks.androidmusic_intentservice D/OpenGLRenderer﹕ Render dirty regions requested: true
01-07 02:29:04.522 2208-2208/com.javacodegeeks.androidmusic_intentservice D/﹕ HostConnection::get() New Host Connection established 0xa6cafee0, tid 2208
01-07 02:29:04.532 2208-2208/com.javacodegeeks.androidmusic_intentservice D/Atlas﹕ Validating map...
01-07 02:29:04.597 2208-2235/com.javacodegeeks.androidmusic_intentservice D/﹕ HostConnection::get() New Host Connection established 0xa6caf960, tid 2235
01-07 02:29:04.602 2208-2235/com.javacodegeeks.androidmusic_intentservice I/OpenGLRenderer﹕ Initialized EGL, version 1.4
01-07 02:29:04.612 2208-2235/com.javacodegeeks.androidmusic_intentservice D/OpenGLRenderer﹕ Enabling debug mode 0
01-07 02:29:04.622 2208-2235/com.javacodegeeks.androidmusic_intentservice W/EGL_emulation﹕ eglSurfaceAttrib not implemented
01-07 02:29:04.622 2208-2235/com.javacodegeeks.androidmusic_intentservice W/OpenGLRenderer﹕ Failed to set EGL_SWAP_BEHAVIOR on surface 0xa6cb0700, error=EGL_SUCCESS
01-07 02:29:18.595 2208-2245/com.javacodegeeks.androidmusic_intentservice W/MessageQueue﹕ Handler (android.media.MediaPlayer$EventHandler) {27bce352} sending message to a Handler on a dead thread
java.lang.IllegalStateException: Handler (android.media.MediaPlayer$EventHandler) {27bce352} sending message to a Handler on a dead thread
at android.os.MessageQueue.enqueueMessage(MessageQueue.java:325)
at android.os.Handler.enqueueMessage(Handler.java:631)
at android.os.Handler.sendMessageAtTime(Handler.java:600)
at android.os.Handler.sendMessageDelayed(Handler.java:570)
at android.os.Handler.sendMessage(Handler.java:507)
at android.media.MediaPlayer.postEventFromNative(MediaPlayer.java:2660)
01-07 02:29:18.597 2208-2248/com.javacodegeeks.androidmusic_intentservice W/MessageQueue﹕ Handler (android.media.MediaPlayer$EventHandler) {27bce352} sending message to a Handler on a dead thread
java.lang.IllegalStateException: Handler (android.media.MediaPlayer$EventHandler) {27bce352} sending message to a Handler on a dead thread
at android.os.MessageQueue.enqueueMessage(MessageQueue.java:325)
at android.os.Handler.enqueueMessage(Handler.java:631)
at android.os.Handler.sendMessageAtTime(Handler.java:600)
at android.os.Handler.sendMessageDelayed(Handler.java:570)
at android.os.Handler.sendMessage(Handler.java:507)
at android.media.MediaPlayer.postEventFromNative(MediaPlayer.java:2660)
但是,当我调试并在以下行上设置断点时:
mMediaPlayer.setOnErrorListener(this);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setOnPreparedListener(this);
try {
mMediaPlayer.prepareAsync();
并逐步执行这些语句,然后在遍历prepareAsync()语句后选择Resume,代码确实到达了onPrepared()方法,音频输出正常。
当我取消注释prepare()和play()语句并注释掉prepareAsynch()语句时,无论是否设置断点,在运行或调试时我都会获得音频输出。 (这就是为什么我留在注释掉的代码中以显示有效的原因。)我知道很幸运,在到达play()方法之前完成了准备工作。
该onError()从未达到,我知道那里有工作要做。
另一件事是,我确实尝试过:
mMediaPlayer.setOnPreparedListener(MusicIntentService.this);
而且似乎没有什么不同。实际上,当我打开“MusicIntentService.this”的监视窗口时,看到的元素和值与“this”相同。
如果有人能指出我正确的方向,我将非常感激。我的下一步是尝试在调用setOnpreparedListener(....)时实现侦听器,但是我想了解为什么当前的实现不能始终如一地工作。
提前致谢。
吉姆
最佳答案
这是我所做的。
我最初的目标是利用prepareAsynch()方法实现一个简单的媒体播放器。这将允许进行更健壮的设计,因为不必只希望媒体播放器在调用start()方法之前就达到准备状态。
如上所述,pskink使我直接意识到IntentService不适合处理媒体播放器控件。
然后我以为我会使用服务,我开始研究按钮的按下操作,并使媒体播放器的状态与UI保持一致。
根据我对Lollipop中包含的功能所做的一些阅读,我回到了从以下位置下载的项目:
http://www.binpress.com/tutorial/using-android-media-style-notifications-with-media-session-controls/165
该 Activity 启动一个服务,该服务处理按钮输入以及媒体播放器控件:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent( getApplicationContext(), MediaPlayerService.class );
intent.setAction( MediaPlayerService.ACTION_PLAY );
startService( intent );
}
}
MediaPlayerService如下:
/**
* Created by paulruiz on 10/28/14.
*/
public class MediaPlayerService extends Service {
public static final String ACTION_PLAY = "action_play";
public static final String ACTION_PAUSE = "action_pause";
public static final String ACTION_REWIND = "action_rewind";
public static final String ACTION_FAST_FORWARD = "action_fast_foward";
public static final String ACTION_NEXT = "action_next";
public static final String ACTION_PREVIOUS = "action_previous";
public static final String ACTION_STOP = "action_stop";
private MediaPlayer mMediaPlayer;
private MediaSessionManager mManager;
private MediaSession mSession;
private MediaController mController;
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void handleIntent( Intent intent ) {
if( intent == null || intent.getAction() == null )
return;
String action = intent.getAction();
if( action.equalsIgnoreCase( ACTION_PLAY ) ) {
mController.getTransportControls().play();
} else if( action.equalsIgnoreCase( ACTION_PAUSE ) ) {
mController.getTransportControls().pause();
} else if( action.equalsIgnoreCase( ACTION_FAST_FORWARD ) ) {
mController.getTransportControls().fastForward();
} else if( action.equalsIgnoreCase( ACTION_REWIND ) ) {
mController.getTransportControls().rewind();
} else if( action.equalsIgnoreCase( ACTION_PREVIOUS ) ) {
mController.getTransportControls().skipToPrevious();
} else if( action.equalsIgnoreCase( ACTION_NEXT ) ) {
mController.getTransportControls().skipToNext();
} else if( action.equalsIgnoreCase( ACTION_STOP ) ) {
mController.getTransportControls().stop();
}
}
private Notification.Action generateAction( int icon, String title, String intentAction ) {
Intent intent = new Intent( getApplicationContext(), MediaPlayerService.class );
intent.setAction( intentAction );
PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 1, intent, 0);
return new Notification.Action.Builder( icon, title, pendingIntent ).build();
}
private void buildNotification( Notification.Action action ) {
Notification.MediaStyle style = new Notification.MediaStyle();
Intent intent = new Intent( getApplicationContext(), MediaPlayerService.class );
intent.setAction( ACTION_STOP );
PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 1, intent, 0);
Notification.Builder builder = new Notification.Builder( this )
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle( "Media Title" )
.setContentText( "Media Artist" )
.setDeleteIntent( pendingIntent )
.setStyle(style);
builder.addAction( generateAction( android.R.drawable.ic_media_previous, "Previous", ACTION_PREVIOUS ) );
builder.addAction( generateAction( android.R.drawable.ic_media_rew, "Rewind", ACTION_REWIND ) );
builder.addAction( action );
builder.addAction( generateAction( android.R.drawable.ic_media_ff, "Fast Foward", ACTION_FAST_FORWARD ) );
builder.addAction( generateAction( android.R.drawable.ic_media_next, "Next", ACTION_NEXT ) );
style.setShowActionsInCompactView(0,1,2,3,4);
NotificationManager notificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
notificationManager.notify( 1, builder.build() );
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if( mManager == null ) {
initMediaSessions();
}
handleIntent( intent );
return super.onStartCommand(intent, flags, startId);
}
private void initMediaSessions() {
mMediaPlayer = new MediaPlayer();
mSession = new MediaSession(getApplicationContext(), "simple player session");
mController =new MediaController(getApplicationContext(), mSession.getSessionToken());
mSession.setCallback(new MediaSession.Callback(){
@Override
public void onPlay() {
super.onPlay();
Log.e( "MediaPlayerService", "onPlay");
buildNotification( generateAction( android.R.drawable.ic_media_pause, "Pause", ACTION_PAUSE ) );
}
@Override
public void onPause() {
super.onPause();
Log.e( "MediaPlayerService", "onPause");
buildNotification(generateAction(android.R.drawable.ic_media_play, "Play", ACTION_PLAY));
}
@Override
public void onSkipToNext() {
super.onSkipToNext();
Log.e( "MediaPlayerService", "onSkipToNext");
//Change media here
buildNotification( generateAction( android.R.drawable.ic_media_pause, "Pause", ACTION_PAUSE ) );
}
@Override
public void onSkipToPrevious() {
super.onSkipToPrevious();
Log.e( "MediaPlayerService", "onSkipToPrevious");
//Change media here
buildNotification( generateAction( android.R.drawable.ic_media_pause, "Pause", ACTION_PAUSE ) );
}
@Override
public void onFastForward() {
super.onFastForward();
Log.e( "MediaPlayerService", "onFastForward");
//Manipulate current media here
}
@Override
public void onRewind() {
super.onRewind();
Log.e( "MediaPlayerService", "onRewind");
//Manipulate current media here
}
@Override
public void onStop() {
super.onStop();
Log.e( "MediaPlayerService", "onStop");
//Stop media player here
NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel( 1 );
Intent intent = new Intent( getApplicationContext(), MediaPlayerService.class );
stopService( intent );
}
@Override
public void onSeekTo(long pos) {
super.onSeekTo(pos);
}
@Override
public void onSetRating(Rating rating) {
super.onSetRating(rating);
}
}
);
}
正如作者所指出的,实际的媒体播放器实例化和实现不存在,但是:“因此,我们现在在锁定屏幕和通知抽屉中有了一个可以正常工作的MediaStyle通知,该通知利用MediaSession进行了播放控制。请尽情享受! ”
我做了几处修改。
主要更改是对initMediaSessions()方法的更改。添加了代码以实例化和初始化媒体播放器。这是添加prepareAsync()和setOnPreparedListener()调用的地方。
还对onPlay()和onPause()回调进行了更改,以包括媒体播放器控制方法。
public void initMediaSessions () {
Uri uri = Uri.parse("http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8" );
//String fileName = "android.resource://" + getPackageName() + "/" + R.raw.georgeharrisonlivinginthematerialworld;
//Uri uri = Uri.parse(fileName);
try {
mMediaPlayer = new MediaPlayer();
} catch (IllegalArgumentException e) {
Toast.makeText(getApplicationContext(), "IllegalArgumentException", Toast.LENGTH_LONG).show();
} catch (SecurityException e) {
Toast.makeText(getApplicationContext(), "SecurityException", Toast.LENGTH_LONG).show();
} catch (IllegalStateException e) {
Toast.makeText(getApplicationContext(), "IllegalStateException", Toast.LENGTH_LONG).show();
}
try {
mMediaPlayer.setDataSource(this, uri);
} catch (IllegalArgumentException e) {
Toast.makeText(getApplicationContext(), "IllegalArgumentException", Toast.LENGTH_LONG).show();
} catch (SecurityException e) {
Toast.makeText(getApplicationContext(), "SecurityException", Toast.LENGTH_LONG).show();
} catch (IllegalStateException e) {
Toast.makeText(getApplicationContext(), "IllegalStateException", Toast.LENGTH_LONG).show();
} catch (IOException e) {
e.printStackTrace();
}
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setOnPreparedListener(this);
try {
mMediaPlayer.prepareAsync();
} catch (IllegalArgumentException e) {
Toast.makeText(getApplicationContext(), "IllegalArgumentException", Toast.LENGTH_LONG).show();
} catch (SecurityException e) {
Toast.makeText(getApplicationContext(), "SecurityException", Toast.LENGTH_LONG).show();
} catch (IllegalStateException e) {
Toast.makeText(getApplicationContext(), "IllegalStateException", Toast.LENGTH_LONG).show();
}
mManager= (MediaSessionManager)getSystemService(Context.MEDIA_SESSION_SERVICE);
mSession = new MediaSession(getApplicationContext(), "simple player session");
mController =new MediaController(getApplicationContext(), mSession.getSessionToken());
mSession.setCallback(new MediaSession.Callback(){
@Override
public void onPlay() {
super.onPlay();
Log.e("MediaPlayerService", "onPlay");
if(first_time_through == true)
{
first_time_through = false;
}
else {
try {
mMediaPlayer.start();
} catch (IllegalArgumentException e) {
Toast.makeText(getApplicationContext(), "IllegalArgumentException", Toast.LENGTH_LONG).show();
} catch (SecurityException e) {
Toast.makeText(getApplicationContext(), "SecurityException", Toast.LENGTH_LONG).show();
} catch (IllegalStateException e) {
Toast.makeText(getApplicationContext(), "IllegalStateException", Toast.LENGTH_LONG).show();
}
}
buildNotification(generateAction(android.R.drawable.ic_media_pause, "Pause", ACTION_PAUSE));
}
@Override
public void onPause() {
super.onPause();
mMediaPlayer.pause();
Log.e( "MediaPlayerService", "onPause");
buildNotification(generateAction(android.R.drawable.ic_media_play, "Play", ACTION_PLAY));
}
@Override
public void onSkipToNext() {
super.onSkipToNext();
Log.e( "MediaPlayerService", "onSkipToNext");
//Change media here
buildNotification( generateAction( android.R.drawable.ic_media_pause, "Pause", ACTION_PAUSE ) );
}
@Override
public void onSkipToPrevious() {
super.onSkipToPrevious();
Log.e( "MediaPlayerService", "onSkipToPrevious");
//Change media here
buildNotification( generateAction( android.R.drawable.ic_media_pause, "Pause", ACTION_PAUSE ) );
}
@Override
public void onFastForward() {
super.onFastForward();
Log.e( "MediaPlayerService", "onFastForward");
//Manipulate current media here
}
@Override
public void onRewind() {
super.onRewind();
Log.e( "MediaPlayerService", "onRewind");
//Manipulate current media here
}
@Override
public void onStop() {
super.onStop();
Log.e( "MediaPlayerService", "onStop");
//Stop media player here
NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel( 1 );
Intent intent = new Intent( getApplicationContext(), MediaPlayerService.class );
stopService( intent );
}
@Override
public void onSeekTo(long pos) {
super.onSeekTo(pos);
}
@Override
public void onSetRating(Rating rating) {
super.onSetRating(rating);
}
}
);
}
该 Activity 使用ACTION_PLAY意向 Action 启动MediaPlayerService:
intent.setAction( MediaPlayerService.ACTION_PLAY );
将first_time_through逻辑添加到onPlay()回调中,以防止在执行onPrepared()回调之前调用mMediaPlayer.start()方法。将以下内容添加到MediaPlayerService类:
public static boolean first_time_through = true;
onPrepared()回调也已添加到MediaPlayerService。
/** Called when MediaPlayer is ready */
public void onPrepared(MediaPlayer player) {
try {
mMediaPlayer.start();
} catch (IllegalStateException e) {
Toast.makeText(getApplicationContext(), "IllegalStateException in onPrepared", Toast.LENGTH_LONG).show();
}
}
由于我尝试使用HLS源进行此操作,因此以下行已添加到AndroidManifest.xml文件中:
<uses-permission android:name="android.permission.INTERNET" />
为了允许锁定屏幕通知控制媒体,还向AndroidManifest.xml文件添加了以下内容:
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
由于目前存在,该应用仅允许两个动态媒体播放器状态,即:“播放”和“暂停”。按下其他按钮可能会导致意外行为。此外,并非所有媒体控制方法都适用于.mp3和HLS源。我将尝试清理。
可以通过修改初始化来替换first_time_through逻辑,但我不确定它们也不会具有竞争条件。
我在使用“Nexus 5 21 API x86”模拟器的MacBook Pro上进行了测试。 .mp3文件似乎可以正常播放。当我使用HLS源时,通常会在大约20秒左右的时间内丢失音频。 HLS源实际上是一个视频文件。当音频丢失时,日志中将显示以下内容:
01-10 01:25:22.074 1219-1237/system_process I/ActivityManager﹕ Waited long enough for: ServiceRecord{3ca21a0b u0 com.google.android.gms/.wearable.service.WearableService}
01-10 01:25:26.204 931-1216/? I/AudioFlinger﹕ BUFFER TIMEOUT: remove(4096) from active list on thread 0xb62b8000
01-10 01:25:34.675 2076-2100/com.android.calendar D/InitAlarmsService﹕ Clearing and rescheduling alarms.
01-10 01:25:59.696 1219-1232/system_process I/MediaFocusControl﹕ AudioFocus abandonAudioFocus() from android.media.AudioManager@1c78d923com.android.music.MediaPlaybackService$3@5af4e20
当我在三星Galaxy S5上使用相同的音频源时,使用简化的(应用程序的API 4.4.2版本),音频可以正常播放。此外,正如预期的那样,从Safari启动时,相同的HLS源可以正常运行。我认为通过仿真器查看HLS回放不会有太多收获。
再次感谢pskink。
吉姆
关于android - 无法使MediaPlayer.prepareAsync正常工作,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27814896/