Right now I'm trying to become more familiar with Android development. Of course, I am using Android Studio to do this. As a project, I am writing an application to handle streaming from a URL with the MediaPlayer
class.
I got this functional and does exactly what I need it to do. However, I did not want my MainActivity to be cluttered with methods and variables that could be stored in their own classes.
I have a couple questions about this:
Is it conventional to do this? Or should I keep all code and methods inside of their respective Activities, or is it conventional to move them to their own class, and access them from whichever Activity needs it?
I am having issues getting the MediaPlayer to work correctly when moving it to another class. I read that I must give the methods
Context
when calling that method. So I called the method using my object withContext
passed, as you will see below.
I am fairly new to this sort of development, so tips, good habits, and helpful hints are always welcome.
Here is my MainActivity
that calls the methods I put in the Streaming
class:
public class MainActivity extends AppCompatActivity {
Streaming stream = new Streaming(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
stream.updateSeekBar();
stream.onPlayClick();
stream.onDrag();
}
You can see here, all of those methods I called:
public class Streaming extends AppCompatActivity {
private SeekBar musicSeek;
private TextView currentTime;
private TextView totalTime;
private ImageButton play_pause;
private Handler seekHandler = new Handler();
private MediaPlayer mp = new MediaPlayer();
Context context;
Utilities util = new Utilities();
//Default Constructor
public Streaming(){}
//Contructor
public Streaming(Context context){
this.context = context;
}
//Method to run the runnable to update the seekbar
public void updateSeekBar(){
seekHandler.postDelayed(mUpdateTimeTask, 100);
}
public void prepareStreaming() throws IOException {
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
mp.setDataSource("http://tricountynaz.net/media/audio/2017-11-08-The%20Compassion%20of%20the%20Christ.mp3");
mp.prepare();
}
public void startStreaming(){
mp.start();
}
public void pauseStreaming(){
mp.pause();
}
//Runnable to update the seekbar with the current position.
private Runnable mUpdateTimeTask = new Runnable() {
public void run() {
int totalDuration = mp.getDuration();
int currentPosition = (mp.getCurrentPosition());
//Displaying Total Duration time
totalTime = (TextView)((Activity)context).findViewById(R.id.totalTime);
totalTime.setText(util.milliSecondsToTimer(totalDuration));
// Displaying time completed playing
currentTime = (TextView)((Activity)context).findViewById(R.id.currentTime);
currentTime.setText(util.milliSecondsToTimer(currentPosition));
//Set the bars total duration, based on the song duration (converted to seconds)
musicSeek = (SeekBar)((Activity)context).findViewById(R.id.music_seek);
musicSeek.setMax(totalDuration/1000);
// Updating progress bar
musicSeek.setProgress(mp.getCurrentPosition()/1000);
// Running this thread after 100 milliseconds
seekHandler.postDelayed(this, 100);
}
};
//What happens when the user interacts with the button
public void onPlayClick (){
play_pause = (ImageButton)((Activity)context).findViewById(R.id.playButton);
play_pause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
play_pause.setImageResource(R.drawable.ic_pause_name);
try {
prepareStreaming();
} catch (IOException e) {
e.printStackTrace();
}
if (mp.isPlaying()) {
pauseStreaming();
play_pause.setImageResource(R.drawable.ic_play_name);
} else {
//mediaPlayer.start();
startStreaming();
}
}
});
}
//Handles when the user interacts with the seekbar
public void onDrag(){
musicSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
if(b) {
//seeks to the current position of the slider when the user (b) interacts.
mp.seekTo(i*1000);
musicSeek.setProgress(i);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});}
You will notice a Utilities
class. This, at the moment, only holds a method to convert to seconds so I can update a TextView
Item of the current progress of the MP3 being streamed.
The specific errors I am receiving are NullPointerException
coming from the onDrag
method. I cannot seem to understand why.
Sorry if this is a duplicate, I could not seem to find this anywhere else.
EDIT I did change my onDrag
method a little:
//Handles when the user interacts with the seekbar
public void onDrag(){
musicSeek = (SeekBar)((Activity)context).findViewById(R.id.music_seek);
musicSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
if(b) {
//seeks to the current position of the slider when the user (b) interacts.
mp.seekTo(i*1000);
musicSeek.setProgress(i);
}
}
This did fix the nullPointerException, except now I get this errors in the log:
E/MediaPlayerNative: Attempt to call getDuration in wrong state: mPlayer=0x0, mCurrentState=0
EDIT 2 I think I resolved my issue. Upon researching my most recent error, I had come to realize I never called my MediaPlayer in my MainActivity, therefore nothing was being loaded and nothing streamed. Here is where I found part of my solution.
Still running into issues with my playbutton
not working and stream starting automatically, but this is a small problem and can be fixed easily I think. However, I would still be interested in knowing if there is something "wrong" with my code.