我一直在尝试使用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/

10-10 22:40