无法使MediaPlayer.prepareAsync工作

时间:2015-01-07 08:05:53

标签: android audio

我一直在尝试使用prepareAsync()方法和onPrepared()方法实现一个简单的MediaPlayer(音频)。这些是从IntentService中调用的。相关活动仅发送开始和停止意图操作。我已经验证了活动正在发送的事件。 以下是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命令上达到准备。当我通过运行或调试执行代码时(模拟器是在MacBook Pro上运行的Nexus 5 API 21 x86,使用Android Studio使用API​​21编译),永远不会到达onPrepared()函数。尝试在三星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);

它似乎没有什么区别。事实上当我打开一个带有&#34; MusicIntentService.this&#34;我看到的相同的元素和价值观与我为#34;这个&#34;。

如果有人能指出我正确的方向,我真的很感激。我的下一步是尝试在调用setOnpreparedListener(....)时实现侦听器,但我想了解为什么当前的实现不能始终如一地工作。 提前致谢。 吉姆

1 个答案:

答案 0 :(得分:0)

这就是我所做的。

我最初的目标是使用prepareAsynch()方法实现一个简单的媒体播放器。这将允许更健壮的设计,因为在调用start()方法之前,不必仅希望媒体播放器达到准备状态。

如上所述,pskink让我直截了当地说IntentService不适合处理媒体播放器控件。

然后我假设我会使用服务,我开始考虑处理按钮按下并保持媒体播放器状态与UI一致。

基于我对Lollipop中包含的功能的一些阅读,我回到了我从下载的项目:

http://www.binpress.com/tutorial/using-android-media-style-notifications-with-media-session-controls/165

活动启动一个处理按钮输入和媒体播放器控制的服务:

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);
        }
        }
    );
}

正如作者所指出的,实际的媒体播放器实例化和实现并不存在,但是:&#34;我们现在在锁定屏幕和利用MediaSession的通知抽屉中有一个完全正常工作的MediaStyle通知用于播放控制。享受&#34;!

我做了几处修改 主要更改是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);
        }
        }
    );
}

活动使用ACTION_PLAY意图操作启动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逻辑,但我不确定它们是否也不会有可能的竞争条件。

我使用&#34; Nexus 5 21 API x86&#34;在MacBook Pro上测试了这个。模拟器。 .mp3文件似乎播放得很好。当我使用HLS源时,音频通常会在大约20秒左右丢失。 HLS源实际上是一个视频文件。当音频丢失时,logcat中会出现以下内容:

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

当我在Samsung Galaxy S5上使用相同的音频源时,使用简化的(API 4.4.2版本的应用程序),音频播放得很好。此外,正如预期的那样,从Safari启动时,相同的HLS源可以正常播放。我不认为通过仿真器查看HLS播放会有太大的收获。

再次感谢pskink。

吉姆