Black screen when returning to video playback activity in Android

I'm currently developing the android application ServeStream and I've encountered and problem that I can't fix. My application will stream music and video using the android MediaPlayer class. I've modeled my class after the example found at:

http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/media/MediaPlayerDemo_Video.html

The difference between this example and my own code is my MediaPlayer is runs in a service which allows it to continue playback in the background. The problem with the example android code is if I'm watching a video and I leave the current window/activity (i.e press the menu button, etc) and return to the playback activity I get a black screen but still receive audio from the video that is playing.

When my playback activity is initially created the code shown below is executed. This code essentially creates the view used for playback and then ties it to the media player:

        setContentView(R.layout.mediaplayer_2);
        mPreview = (SurfaceView) findViewById(R.id.surface);
        holder = mPreview.getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
...
        mMediaPlayer.setDisplay(holder);

The important line is mMediaPlayer.setDisplay(holder) because it ties the current view/display to the media player. The view (the "holder") is destroyed when you leave the activity. After returning to the activity and recreating the view, executing mMediaPlayer.setDisplay(holder) again doesn't appear to re-attach the newly created view. A black screen is shown instead of the video.

Does anyone have a workaround or solution for this issue. I would appreciate any help or advice.

Answers


After lot of googling around and head-scratching over state diagram of MediaPlayer, I finally managed to avoid this so called black screen. I've already posted in the OP's comments section that this problem seems to be resolved with latest Android versions (greater than 4.x)and so I was opting to have similar behavior on Gingerbread devices as well.

Needless to say, SurfaceHolder callback methods play a very crucial role in the lifecycle of a bounded MediaPlayer-SurfaceView implementation. And those very callback methods came handy to me for getting out of this situation.

First:

inside onCreate(): - Basic initialization stuff..

    mVideoSurface = (SurfaceView) findViewById(R.id.videoSurface);
    videoHolder = mVideoSurface.getHolder();
    videoHolder.addCallback(this);

    // For Android 2.2

    videoHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    // Media Player and Controller

    player = new MediaPlayer();
    controller = new VideoControllerView(this);

Then, SurfaceView Callbacks :

don't forget to set the Display to your MediaPlayer and IMHO, surfaceCreated() is the best place to do that.

@Override
 public void surfaceCreated(SurfaceHolder holder) {
    player.setDisplay(holder);
    try {
    player.prepareAsync();
    } catch (IllegalStateException e) {
        e.printStackTrace();
    }
}

And here's the most important thing. When user leaves the current activity by either pressing Home button or by opening a different activity with expecting any results, our SurfaceView is going to to be get destroyed. What I wanted to achieve was to resume the playback of ongoing video from the position it was playing when the context got switched. So, in order to do that,

Save the current playback position in a variable so that we can use it later on to seek our playback to that particular position. And one more. Release the damn MediaPlayer instance. I tried to avoid releasing the MediaPlayer instance and it's re-creation but I keep failing over and over. So..point made.

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    if (player != null) {
        mCurrentVideoPosition = player.getCurrentPosition();
        player.release();
        player = null;
    }

}

Now, onPrepared() Initialize any MediaController here if you're interested. Check if the video was already playing and so seek the video to that position.

     @Override
 public void onPrepared(MediaPlayer mp) {
    controller.setMediaPlayer(this);
    controller.setAnchorView((FrameLayout) findViewById(R.id.videoSurfaceContainer));
    player.start();

    if (mCurrentVideoPosition != 0) {
        player.seekTo(mCurrentVideoPosition);
        player.start();
    }

}

Finally, to play the video:

 void playVideo(){  

   try {
        if (player == null) 
            player = new MediaPlayer();
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        player.setDataSource("SOME_VIDEO_FILE.3gp");
        player.setOnPreparedListener(this);
        player.setOnErrorListener(new OnErrorListener() {

            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                mp.reset();
                return false;
            }
        });

    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalStateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
     }

And that's all of it. I know the issue is not there with newer versions and all great but... lots of GBs are still out there.


You can do: mMediaPlayer.setDisplay(null) when surfaceDestroy and when you enter video again setDisplay for mediaPlayer with new surfaceHolder. Remember, always put this code below inside onStart, because when home pressed or lockscreen, it will force `sufaceCreate' fired with new holder. View will be recreated, video will show instead black screen

    holder = mPreview.getHolder();
    holder.addCallback(this);
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

Hope this help!!


I think I know the cause of the problem. It seems that mediaplayer initializes its surface ONLY when you call prepare/Async(). If you will change the surface after that, you will get what you`ve got. So the solution would be to disable standard lifecycle of activity via android:configChanges="orientation|keyboardHidden". This will prevent your activity from recreations. Otherwise you should call for prepare each time you recreate holder.


I have the same issue!

While video is playing I press HOME button and then return to the Application. I get in:

public void surfaceCreated(SurfaceHolder holder) {
    player.setDisplay(holder);
    playVideo();
}

In the playVideo() I have:

private void playVideo() {
    if (extras.getString("video_path").equals("VIDEO_URI")) {
        showToast("Please, set the video URI in HelloAndroidActivity.java in onClick(View v) method");
    } else {
        try {
            if (flag == 0) {
                player.setDataSource(extras.getString("video_path"));
                player.prepareAsync();
                flag = 1;
            }
            else
            {
                player.start();
            }
        } catch (IllegalArgumentException e) {
            showToast("Error while playing video");
            Log.i(TAG, "========== IllegalArgumentException ===========");
            e.printStackTrace();
        } catch (IllegalStateException e) {
            showToast("Error while playing video");
            Log.i(TAG, "========== IllegalStateException ===========");
            e.printStackTrace();
        } catch (IOException e) {
            showToast("Error while playing video. Please, check your network connection.");
            Log.i(TAG, "========== IOException ===========");
            e.printStackTrace();
        }
    }
}

And I have blank black background and hear audiostream.

I noticed that if in the first time of launching this Activity if I don't make player.setDisplay(holder); I will have the same behavior.

A spent two days trying to solve this issue...


EDIT: Please note this solution does not work on 4.x devices - video simply never appears, only blank screen and I couldn't make it work there. Here's a state machine that does the following: 1. plays short couple of seconds video /res/raw/anim_mp4.mp4 (procedure initiated in onCreate() method) 2. if user presses home button then returns to app it will seek video to start position and pause immediatelly (procedure initiated in onResume() method)

package com.test.video;
import android.app.Activity;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;

public class MediaPlayerActivity extends Activity implements OnCompletionListener,
    MediaPlayer.OnPreparedListener, MediaPlayer.OnSeekCompleteListener, SurfaceHolder.Callback {

private final int STATE_NOT_INITIALIZED = 0;
private final int STATE_INITIALIZING = 1;
private final int STATE_INITIALIZED = 2;
private final int STATE_SEEKING = 3;
private final int STATE_DELAYING = 4;
private final int STATE_PLAYING = 5;
private final int STATE_FINISHED = 6;
private final int STATE_RESUMED = 7;
private final int STATE_RESUMED_PREPARING = 8;
private final int STATE_RESUMED_PREPARED = 9;
private final int STATE_RESUMED_SEEKING = 10;
private int state = STATE_NOT_INITIALIZED;

private SurfaceView surface;
private MediaPlayer player;
private SurfaceHolder holder;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Log.d(Constants.TAG, "onCreate()");
    super.onCreate(savedInstanceState);

    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);
    setContentView(R.layout.mediaplayer);

    surface = (SurfaceView) findViewById(R.id.idSurface);
    holder = surface.getHolder();
    holder.addCallback(this);
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

@Override
protected void onStart() {
    Log.d(Constants.TAG, "onStart()");
    super.onStart();
    state();
}

@Override
protected void onResume() {
    Log.d(Constants.TAG, "onResume()");
    super.onResume();
    if (STATE_FINISHED == state) {
        state = STATE_RESUMED;
        state();
    }
}

@Override
protected void onDestroy() {
    Log.d(Constants.TAG, "onDestroy()");
    super.onDestroy();
    if (player != null) {
        player.release();
        player = null;
    }
}

@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    Log.d(Constants.TAG, "surfaceChanged()");
}

@Override
public void surfaceCreated(SurfaceHolder arg0) {
    Log.d(Constants.TAG, "surfaceCreated()");
}

@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
    Log.d(Constants.TAG, "surfaceDestroyed()");
}

@Override
public void onPrepared(MediaPlayer mp) {
    Log.d(Constants.TAG, "onPrepared()");
    state();
}

@Override
public void onCompletion(MediaPlayer mp) {
    Log.d(Constants.TAG, "onCompletion()");
    state();
}

@Override
public void onSeekComplete(MediaPlayer mediaplayer) {
    Log.d(Constants.TAG, "onSeekComplete()");
    state();
}

private class ResumeDelayed extends PlayDelayed {
    protected void onPostExecute(Void result) {
        Log.d(Constants.TAG, "ResumeDelayed.onPostExecute()");
        state();
    };
}

private void initPlayer() {
    Log.d(Constants.TAG, "initPlayer()");
    try {
        if (player == null) {
            player = new MediaPlayer();
            player.setScreenOnWhilePlaying(true);
        } else {
            player.stop();
            player.reset();
        }
        String uri = "android.resource://" + getPackageName() + "/" + R.raw.anim_mp4;
        player.setDataSource(this, Uri.parse(uri));
        player.setDisplay(holder);
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        player.setOnPreparedListener(this);
        player.prepareAsync();
        player.setOnCompletionListener(this);
        player.setOnSeekCompleteListener(this);
    } catch (Throwable t) {
        Log.e(Constants.TAG, "Exception in media prep", t);
    }
}

private class PlayDelayed extends AsyncTask<Void, Void, Void> {
    @Override
    protected Void doInBackground(Void... arg0) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Log.e(Constants.TAG, "Can't sleep", e);
        }
        return null;
    }

    protected void onPostExecute(Void result) {
        Log.d(Constants.TAG, "PlayDelayed.onPostExecute()");
        initPlayer();
    };
}

private void state() {
    switch (state) {
        case STATE_NOT_INITIALIZED:
            state = STATE_INITIALIZING;
            initPlayer();
            break;
        case STATE_INITIALIZING:
            state = STATE_INITIALIZED;
            new PlayDelayed().execute();
            break;
        case STATE_INITIALIZED:
            state = STATE_SEEKING;
            player.start();
            player.seekTo(0);
            break;
        case STATE_SEEKING:
            state = STATE_DELAYING;
            player.pause();
            new ResumeDelayed().execute();
            break;
        case STATE_DELAYING:
            state = STATE_PLAYING;
            player.start();
            break;
        case STATE_PLAYING:
            state = STATE_FINISHED;
            break;
        case STATE_RESUMED:
            state = STATE_RESUMED_PREPARING;
            initPlayer();
            break;
        case STATE_RESUMED_PREPARING:
            state = STATE_RESUMED_PREPARED;
            new PlayDelayed().execute();
            break;
        case STATE_RESUMED_PREPARED:
            state = STATE_RESUMED_SEEKING;
            player.start();
            player.seekTo(0);
            break;
        case STATE_RESUMED_SEEKING:
            state = STATE_FINISHED;
            player.pause();
            break;
        default:
            break;
    }
}

mediaplayer.xml layout:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/black" >

<SurfaceView
    android:id="@+id/idSurface"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center" />

AndroidManifest.xml looks like:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.video"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk android:minSdkVersion="10" />

<application
    android:icon="@drawable/ic_launcher"
    android:theme="@android:style/Theme.Black.NoTitleBar" >
    <activity
        android:name=".MediaPlayerActivity"
        android:label="@string/app_name"
        android:screenOrientation="portrait"
        android:theme="@android:style/Theme.Black.NoTitleBar" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>


I saw the same issue simply playing video on my Galaxy S3 and on a Xoom Tablet. I turned off hardware (HW) acceleration in the player settings and it solved the problem. I don't develop for Android but hopefully this will lead you to a solution. Maybe you can toggle this setting for a workaround. The device I'm using might not have HW acceleration.


Do you want the activity not to be shown when you return? if yes you can simply use flag

i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

If you want your media to be played in background I suggest you to use video surface as it has been irritating me playing in background though I dont want. There are ways but I hope this one will be fruitful for you http://www.brightec.co.uk/blog/custom-android-media-controller

this link actually is for customizing media controllers as you wish but as I created mine with it, I was bothered as pressing home key, the video would be played in background.


Need Your Help

iframe reaches bottom of page

html iframe styles height

Is there a way to make the height of the &lt;iframe&gt; reach exactly the bottom of the page? It is hard to judge by using height:xx%, and it might be dependent on browser.

How to repaint my JTable

java swing jtable repaint

So I have this application with a JTable in it. The JTable is inside of a JScrollPane and JScrollPane is painted on a JFrame.