Challenge: Playing Video in HelloMoon solution


#1

Be sure to convert apollo_17_stroll to mp4 (google mpg to mp4 and a few sites appear which let you convert for free online). Otherwise, MediaPlayer.create() returns null.

I added a button to HelloMoonFragment and when pressed a new Activity (VideoContainerActivity) hosting a fragment (VideoContainerFragment) is created. The fragment contains a SurfaceView for our video. On playback completion we finish our activity which takes us back to HelloMoonActivity.

HelloMoonActivity is unchanged.

HelloMoonFragment.java

[code]
package com.bignerdranch.android.hellomoon;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class HelloMoonFragment extends Fragment {

private AudioPlayer mPlayer = new AudioPlayer();
private Button mPlayButton;
private Button mStopButton;
private Button mPauseButton; // challenge 1: Created a pause button.
private Button mVideoButton; // challenge 2: Created a video button, when pressed, a new Activity hosting a Fragment is created.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
{
	View v = inflater.inflate(R.layout.fragment_hello_moon, parent, false);

	mPlayButton  = (Button) v.findViewById(R.id.hellomoon_playButton);
	mStopButton  = (Button) v.findViewById(R.id.hellomoon_stopButton);
	mPauseButton = (Button) v.findViewById(R.id.hellomoon_pauseButton); // challenge 1: get ref to pause button.
	mVideoButton = (Button) v.findViewById(R.id.hellomoon_videoButton); // challenge 2: get ref to video button.

	mPlayButton.setOnClickListener(new PlayButtonListener());
	mStopButton.setOnClickListener(new StopButtonListener());
	mPauseButton.setOnClickListener(new PauseButtonListener());
	mVideoButton.setOnClickListener(new VideoButtonListener());

	return v;
}

@Override
public void onDestroy()
{
	super.onDestroy();
	mPlayer.stop();
}

class PlayButtonListener implements View.OnClickListener {
	@Override
	public void onClick(View view)
	{
		mPlayer.start(getActivity());
	}
}

class StopButtonListener implements View.OnClickListener {
	@Override
	public void onClick(View view)
	{
		mPlayer.stop();
	}
}

// challenge 1: create a listener for pause button.
class PauseButtonListener implements View.OnClickListener {
	@Override
	public void onClick(View view)
	{
		mPlayer.pause();
	}
}

// challenge 2: create a listener for video button.
class VideoButtonListener implements View.OnClickListener {
	@Override
	public void onClick(View view)
	{
		mPlayer.stop(); // audio may be playing when user presses the video button.
		Intent i = new Intent(getActivity(), VideoContainerActivity.class);
		startActivity(i);
	}
}

}
}[/code]

VideoContainerActivity.java

package com.bignerdranch.android.hellomoon;


import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class VideoContainerActivity extends FragmentActivity {

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_hello_moon_video);
	}
}

VideoContainerFragment.java

package com.bignerdranch.android.hellomoon;

import android.media.MediaPlayer;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;


public class VideoContainerFragment extends Fragment {

	private MediaPlayer   mPlayer;
	private SurfaceHolder mSurfaceHolder;
	private SurfaceView   mSurfaceView;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
	{
		View v = inflater.inflate(R.layout.fragment_hello_moon_video, container, false);

		mSurfaceView = (SurfaceView) v.findViewById(R.id.videoSurface);
		mSurfaceHolder = mSurfaceView.getHolder();
		mSurfaceHolder.addCallback(new SurfaceHolderCallback());

		return v;
	}

	class SurfaceHolderCallback implements SurfaceHolder.Callback {
		@Override
		public void surfaceCreated(SurfaceHolder surfaceHolder)
		{
			mPlayer = MediaPlayer.create(getActivity(), R.raw.apollo_17_stroll);
			mPlayer.setDisplay(mSurfaceHolder);
			mPlayer.setOnCompletionListener(new VideoCompletionListener());
			mPlayer.setOnPreparedListener(new VideoPreparedListener());
		}

		@Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) { }
		@Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { }
	}

	class VideoCompletionListener implements MediaPlayer.OnCompletionListener {

		@Override
		public void onCompletion(MediaPlayer mediaPlayer)
		{
			getActivity().finish();
		}
	}

	class VideoPreparedListener implements MediaPlayer.OnPreparedListener {
		@Override
		public void onPrepared(MediaPlayer mediaPlayer)
		{
			mPlayer.start();
		}
	}
}

activity_hello_moon_video.xml

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:id="@+id/videoContainerFragment"
          android:name="com.bignerdranch.android.hellomoon.VideoContainerFragment">
</fragment>

fragment_hello_moon_video.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/videoSurface"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

Below is AudioPlayer.java for anyone curious about the solution for first challenge. Notice the boolean field I added.

AudioPlayer.java

package com.bignerdranch.android.hellomoon;

import android.content.Context;
import android.media.MediaPlayer;

public class AudioPlayer {

	private MediaPlayer mPlayer;
	private boolean mIsPaused = false;

	public void stop()
	{
		if (mPlayer != null) {
			mPlayer.release();
			mPlayer = null;
			mIsPaused = false;
		}
	}

	public void start(Context c)
	{
		if (mPlayer != null) if (mPlayer.isPlaying()) return; // prevents audio from restarting from beginning when play button is pressed while audio is playing.

		if (!mIsPaused) {
			stop();

			mPlayer = MediaPlayer.create(c, R.raw.one_small_step);
			mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
			@Override
			public void onCompletion(MediaPlayer mediaPlayer)
			{
				stop();
			}
		});
		}

		mPlayer.start();
		mIsPaused = false;
	}

	public void pause()
	{
		if (mPlayer != null) {
			if (mPlayer.isPlaying()) {
				mIsPaused = true;
				mPlayer.pause();
			}
		}
	}
}

#2

I got this working, but with a few hurdles that needed to be overcome.

The first and most important is to retool the video into a supported format. The mpg video was unusable, resulting in the MediaPlayer not being created, resulting in a crash that brought the program to an ugly halt. Once the video was replaced with an mp4 version, the crash was abated.

I think it would be good for Big Nerd Ranch to either offer a mpeg4 version of the file, or replace the mgp version with a mpeg4 version.

Once using the video version that MediaPlayer could work with, there was only a few other changes.

First was to modify AndroidManifest.xml to include:

 <activity android:name=".VideoContainerActivity" />

Without the movie format correction, VideoContainerFragment.java suffers a crash when loading.
change this:

      public void surfaceCreated(SurfaceHolder surfaceHolder)
      {
         mPlayer = MediaPlayer.create(getActivity(), R.raw.apollo_17_stroll);
         mPlayer.setDisplay(mSurfaceHolder);
         mPlayer.setOnCompletionListener(new VideoCompletionListener());
         mPlayer.setOnPreparedListener(new VideoPreparedListener());
      }

to:

      public void surfaceCreated(SurfaceHolder surfaceHolder)
      {
         mPlayer = MediaPlayer.create(getActivity(), R.raw.apollo_17_stroll);
         if (mPlayer != null)
        {
             mPlayer.setDisplay(mSurfaceHolder);
             mPlayer.setOnCompletionListener(new VideoCompletionListener());
             mPlayer.setOnPreparedListener(new VideoPreparedListener());
         }
      }

to allow for non-existing mPlayer due to incompatable movie format.

Also I changed the of fragment_hello_moon_video.xml file to render the video undistorted and centered (without this, the video fills the screen and is aspect distorted:

        android:layout_width="320dp"
        android:layout_height="240dp"
        android:layout_gravity="center"

The most important thing is to convert the movie to mpeg4! I tried without converting it (on Android 4.4) and it crashed


#3

Yes, this method works almost perfectly.
But if you push BACK button on your phone, during video playing - there will be an error:

LogCat worried about this lines ( getActivity().finish(); ):

  class VideoCompletionListener implements MediaPlayer.OnCompletionListener {

        @Override
        public void onCompletion(MediaPlayer mediaPlayer) {

            getActivity().finish();

        }
    }

But if you watch video clip for the end, app works normal. It come back to the previous activity.

Any ideas - how to fix it?


#4

I have implemented the challenge, however, I can only hear the sound but no movie is coming up on the screen

[code]private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private MediaPlayer mPlayer;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
		Bundle savedInstanceState) {

	View view = inflater.inflate(R.layout.fragment_hello_moon_video,
			container, false);

	mSurfaceView = (SurfaceView) view.findViewById(R.id.hellomoon_surface);
	mSurfaceHolder = mSurfaceView.getHolder();
	mSurfaceHolder.addCallback(new Callback() {

		@Override
		public void surfaceDestroyed(SurfaceHolder holder) {
		}

		@Override
		public void surfaceChanged(SurfaceHolder holder, int format,
				int width, int height) {
		}

		@Override
		public void surfaceCreated(SurfaceHolder holder) {
			mSurfaceHolder = holder;
			mPlayer = MediaPlayer.create(getActivity(),
					R.raw.apollo_17_stroll);
			Log.d("Ashish", (holder == null) + " is true if null");
			mPlayer.setDisplay(holder);

			mPlayer.setOnCompletionListener(new OnCompletionListener() {

				@Override
				public void onCompletion(MediaPlayer mp) {

					mPlayer.release();
					mPlayer = null;
				}
			});

			mPlayer.setOnPreparedListener(new OnPreparedListener() {

				@Override
				public void onPrepared(MediaPlayer mp) {
					mp.start();

				}
			});

		}
	});

	return view;
}

@Override
public void onDestroy() {

	mPlayer.release();
	mPlayer = null;
	super.onDestroy();
}[/code]

[code]<?xml version="1.0" encoding="utf-8"?>

<SurfaceView
    android:id="@+id/hellomoon_surface"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

[/code]


#5

[quote]Yes, this method works almost perfectly.
But if you push BACK button on your phone, during video playing - there will be an error:

LogCat worried about this lines ( getActivity().finish(); ):[/quote]

  1. you need to call mediaPlayer.release() in onCompletion() and set it to null if you are doing a null check for it
  2. you need to override onDestroy in your fragment because back button destroys the activity

@Override public void onCompletion(MediaPlayer mediaPlayer) { mediaPlayer.release(); mediaPlayer = null; getActivity().finish(); }

@Override public void onDestroy() { super.onDestroy(); mPlayer.release(); }


#6

The most important point that wasted my time was that the provided video was not compatible with MediaPlayer and the create method of MediaPlayer would return null.

Way to go with impossible to do challenges BNR :smiley:


#7

Thanks jeffdz for posting the solution and everyone’s improvement.

For ashishb, I have 2 devices. Same code, but my HTC Evo 4g (api 10) did not show video (played sound), but my Samsung Galaxy S3 (api 19) showed the video.

Not really sure why, but oh well.


#8

It didn’t work here.Every time I tried to press the video button, my application stopped immediately.
I still didn’t find the reason.And there is no video window.Any suggestion?


#9

And I also want to ask where you put the mp4 file.I guess maybe it’s because I put it on the wrong place.