I use MediaPlayer for playing a single MP3 song from network. Data source is the file downloaded from network. This files comes in chuncks.
Let's assume we have following playing state.
Song duration: 4:00
Current chunck loaded in player: 1:00
Let's say I want to skip some part of a song and seek forward. I do it with MediaPlayer.seekTo() method. If I seek to position (1:40) it is not performed correctly, seek bar goes at start position (00:00) and the playerr stops.
public static void seekTo(int progress) {
try {
MediaPlayer mp = new MediaPlayer();
mp.setDataSource(sFilePath);
mp.prepare();
int offset = (progress * mp.getDuration()) / 100;
if (sCompleted)
return;
sLastSeek = offset;
if (offset > sMediaPlayer.getDuration()) {
sMediaPlayer.reset();
sMediaPlayer.setDataSource(sFilePath);
sMediaPlayer.prepareAsync();
} else {
sMediaPlayer.seekTo(offset);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
Related
I have a list of song objects that I want to iterate through using a MediaPlayer. It works, but the first time I launch the app, it races through the first song (sometimes several seconds, sometimes the whole song). If I completely close and reopen the app, it is fine in subsequent runs. Why is it doing this, and how can I avoid it?
My SoundPlayerMedia class is:
private MediaPlayer _soundPlayer;
public SoundPlayerMedia(Context applicationContext) {
super();
_soundPlayer = MediaPlayer.create(applicationContext, R.raw.ringtone_cesium);
}
private void initializeMedia(Context context, int soundID, boolean startPlaying) {
AssetFileDescriptor afd = context.getResources().openRawResourceFd(soundID);
try {
if (_soundPlayer == null)
return;
_soundPlayer.reset();
_soundPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
_soundPlayer.setVolume(1f, 1f); // Forces MAX volume
_soundPlayer.prepare();
if (startPlaying)
_soundPlayer.start();
afd.close();
} catch (Exception e) {
Log.e("DEBUG", "Unable to switch sound due to exception: " + e.getMessage(), e);
}
}
I use this in a fragment. In the onViewCreated, I have:
_fSoundPlayer = new SoundPlayerMedia(getContext().getApplicationContext());
_fSoundPlayer.setLooping(false);
_soundPlayer.initializeSound(getContext(), currSongId, startPlaying);
I create a MediaPlayer like in the appended code. Everything works fine, but I've one problem: whenever an alarm is played and I'm connected to headphones, the sound is played on the phone and in the headphones. I would like the phone to be quiet in such a situation.
Actually, if I use the same function to create another music player to play music and set the stream type to AudioManager.STREAM_MUSIC, everything works fine.
I'm getting this behaviour on an android 4.4.4 phone and I know, that this code worked as expected on my old phone... With android 4.3 I think...
int streamVolume = ((AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE)).getStreamVolume(AudioManager.STREAM_ALARM);
if (streamVolume != 0)
{
mAlarmPlayer = createMediaPlayerIfNeeded(mAlarmPlayer, true, true, false);
try
{
mAlarmPlayer.setDataSource(this, Uri.parse(sound));
mAlarmPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
mAlarmPlayer.setVolume(streamVolume, streamVolume);
mAlarmPlayer.prepareAsync();
}
catch (IOException e)
{
Log.e("MusicService", "IOException playing alarm: " + e.getMessage());
e.printStackTrace();
}
}
And here is the create function:
private MediaPlayer createMediaPlayerIfNeeded(MediaPlayer player, boolean setListenerOnPrepared, boolean setListenerOnCompletion, boolean setListenerOnError)
{
if (player == null)
{
player = new MediaPlayer();
player.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
if (setListenerOnPrepared)
player.setOnPreparedListener(this);
if (setListenerOnCompletion)
player.setOnCompletionListener(this);
if (setListenerOnError)
player.setOnErrorListener(this);
}
else
player.reset();
return player;
}
My objective is to create a VideoView that can play videos in a pre-defined play list.
I'm trying to use MediaPlayer.setNextMediaPlayer(...) to allow a seamless transition between two videos. However, when the first video finishes playing, the 2nd video will not start automatically as it should according to the documentation.
Xamarin Android Code:
Queue<MediaPlayer> MediaPlayerList = null;
private void PlayVideo()
{
MediaPlayerList = new Queue<MediaPlayer>();
//Let's go ahead and create all media players
VideoView_CurrentVideoView = new VideoView(this);
VideoView_CurrentVideoView.Completion += mVideoView_Completion;
VideoView_CurrentVideoView.Prepared += mVideoView_Prepared;
//Let's prepare all MediaPlayer
for (int i = 0; i < VideoView_CurrentVideoChannel.VideoAssetList.Count; i++)
{
string filePath = FilePath[i];
if (i == 0)
{
VideoView_CurrentVideoView.SetVideoPath(filePath);
VideoContainer.AddView(VideoView_CurrentVideoView);
}
else
{
MediaPlayer mpNew = new MediaPlayer();
mpNew.SetDataSource(filePath);
MediaPlayerList.Enqueue(mpNew);
}
}
VideoView_CurrentVideoView.Start();
}
void mVideoView_Completion(object sender, EventArgs e)
{
MediaPlayer mp = (MediaPlayer)sender;
mp.Release();
}
void mVideoView_Prepared(object sender, EventArgs e)
{
MediaPlayer mp = (MediaPlayer)sender;
//Take the next available MediaPlayer from the queue
MediaPlayer nextMediaPlayer = MediaPlayerList.Dequeue();
//Put the current MediaPlayer at the end of the queue
MediaPlayerList.Enqueue(mp);
nextMediaPlayer.Prepare();
mp.SetNextMediaPlayer(nextMediaPlayer);
}
Any help or suggestions will be greatly appreciated. This is coded in Xamarin Android.
Update #1: After moving the .Prepare() away from Prepared() event
Queue<string> VideoListQueue = null;
MediaPlayer NextMediaPlayer = null;
private void PlayVideo()
{
string filePath = FilePath[0];
//Create video view
if (VideoContainer.ChildCount == 0)
{
//setup the videoview container
VideoView_CurrentVideoView = new VideoView(this);
VideoView_CurrentVideoView.Info += VideoView_CurrentVideoView_Info;
VideoView_CurrentVideoView.Error += VideoView_CurrentVideoView_Error;
LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.FillParent);
param.LeftMargin = 0;
param.RightMargin = 0;
param.BottomMargin = 0;
param.TopMargin = 0;
VideoView_CurrentVideoView.LayoutParameters = param;
VideoView_CurrentVideoView.LayoutParameters.Width = ViewGroup.LayoutParams.FillParent;
VideoView_CurrentVideoView.LayoutParameters.Height = ViewGroup.LayoutParams.FillParent;
VideoView_CurrentVideoView.Completion += VideoView_CurrentVideoView_Completion;
VideoView_CurrentVideoView.Prepared += VideoView_CurrentVideoView_Prepared;
VideoContainer.AddView(VideoView_CurrentVideoView);
}
VideoView_CurrentVideoView.SetVideoPath(filePath);
VideoView_CurrentVideoView.SeekTo(0);
VideoView_CurrentVideoView.Start();
}
void VideoView_CurrentVideoView_Prepared(object sender, EventArgs e)
{
//Do nothing at this moment
MediaPlayer mp = (MediaPlayer)sender;
}
void VideoView_CurrentVideoView_Completion(object sender, EventArgs e)
{
//GC the finished MediaPlayer
MediaPlayer mp = (MediaPlayer)sender;
mp.Reset();
mp.Release();
mp = null;
//Preparing the next MediaPlayer
MediaPlayer currentPlayer = NextMediaPlayer;
NextMediaPlayer = SetupNextMediaPlayer();
currentPlayer.SetNextMediaPlayer(NextMediaPlayer);
}
MediaPlayer SetupNextMediaPlayer()
{
MediaPlayer mp = new MediaPlayer();
//When the video start playing, let's get ready for next one
string sourceURL = VideoListQueue.Dequeue();
VideoListQueue.Enqueue(sourceURL);
string filePath = sourceURL;
mp = new MediaPlayer();
mp.Info += VideoView_CurrentVideoView_Info;
mp.Completion += VideoView_CurrentVideoView_Completion;
mp.Prepared += VideoView_CurrentVideoView_Prepared;
mp.SetDataSource(filePath);
mp.Prepare();
//Fire back the created MediaPlayer object to the caller
return mp;
}
void VideoView_CurrentVideoView_Info(object sender, MediaPlayer.InfoEventArgs e)
{
Console.WriteLine("What = " + e.What);
switch (e.What)
{
case MediaInfo.VideoRenderingStart:
{
//This is only happening on video first started
NextMediaPlayer = SetupNextMediaPlayer();
e.Mp.SetNextMediaPlayer(NextMediaPlayer);
break;
}
}
}
void VideoView_CurrentVideoView_Error(object sender, MediaPlayer.ErrorEventArgs e)
{
e.Handled = true;
}
At this moment, the media player will begin playing the 2nd video once the first one is done. However, the 2nd video only have sound and no video showing.
Anyone know what I did wrong? I have a feeling that it has to do something with the MediaPlayer not attached to the SurfaceView. However, I created the view using VideoView, how can I get the Surface from VideoView?
Regarding playing 2nd video only with sound: try to implement OnCompletionListener listener for each MediaPlayer with this:
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
mp.setDisplay(null); //for current mediaPlayer
nextMediaPlayer.setDisplay(getHolder()); //for next video
}
});
I can't say that is is gapless, but somehow it works. To archive this I don't use standard VideoView, but custom View, that extends from SurfaceView.
what if you prepare and play the next mediaplayer on .Completion event? have you try it? although it may have a small delay
After many years of testing, this problem does not happen on all hardware. For example, I run the same APK on Nexus7, it appears to be seamless and everything is working. In contrast, if I run it on Amlogic media player board, it will render the above-described problem.
I decided to close this post off with a conclusion that it is something to do with the hardware. I know someone overcome this limitation by run everything in OpenGL, but that is completely a separate beast to deal with.
Conclusion
If you are having a similar problem as described above, there's nothing much you can do as it is heavily dependent on the hardware.
I'm working on Video streaming over Delay-Tolerant Networks using android mobiles. For that im trying to implement progressive video download based on the tutorial given by pocket journey
http://blog.infidian.com/2008/04/04/tutorial-custom-media-streaming-for-androids-mediaplayer/
While creating a new media player the prepareAsync function is blocking the video display, making streaming unbearable. Is there a way to display something while the media player is prepared?
I'm using SurfaceView and SUrfaceHolder to display the video as suggested in the API media demos.
private MediaPlayer createMediaPlayer(File mediaFile)
throws IOException {
MediaPlayer mPlayer = new MediaPlayer();
mPlayer.setOnErrorListener(
new MediaPlayer.OnErrorListener() {
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.e(TAG, "Error in MediaPlayer: (" + what +") with extra (" +extra +")" );
return true;
}
});
// It appears that for security/permission reasons, it is better to pass a FileDescriptor rather than a direct path to the File.
// Also I have seen errors such as "PVMFErrNotSupported" and "Prepare failed.: status=0x1" if a file path String is passed to
// setDataSource(). So unless otherwise noted, we use a FileDescriptor here.
FileInputStream fis = new FileInputStream(mediaFile);
mPlayer.setDataSource(fis.getFD());
mPlayer.setDisplay(holder);
mPlayer.prepareAsync();
return mPlayer;
}
/**
* Transfer buffered data to the MediaPlayer.
* NOTE: Interacting with a MediaPlayer on a non-main UI thread can cause thread-lock and crashes so
* this method should always be called using a Handler.
*/
private void transferBufferToMediaPlayer() {
try {
// First determine if we need to restart the player after transferring data...e.g. perhaps the user pressed pause
final boolean wasPlaying = mediaPlayer.isPlaying();
final int curPosition = mediaPlayer.getCurrentPosition();
Log.e(TAG, "Current position: "+curPosition);
final MediaPlayer temp = mediaPlayer;
// Copy the currently downloaded content to a new buffered File. Store the old File for deleting later.
File oldBufferedFile = new File(context.getCacheDir(),"playingMedia" + counter + ".dat");
File bufferedFile = new File(context.getCacheDir(),"playingMedia" + (counter++) + ".dat");
// This may be the last buffered File so ask that it be delete on exit. If it's already deleted, then this won't mean anything. If you want to
// keep and track fully downloaded files for later use, write caching code and please send me a copy.
bufferedFile.deleteOnExit();
moveFile(downloadingMediaFile,bufferedFile);
// Pause the current player now as we are about to create and start a new one. So far (Android v1.5),
// this always happens so quickly that the user never realized we've stopped the player and started a new one
if(wasPlaying){
Log.e(TAG, "mediaPlayer paused");
mediaPlayer.pause();
}
//mediaPlayer.release();
// Create a new MediaPlayer rather than try to re-prepare the prior one.
mediaPlayer = createMediaPlayer(bufferedFile);
mediaPlayer.setOnPreparedListener(
new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mPlayer) {
// TODO Auto-generated method stub
mediaPlayer.seekTo(curPosition);
// Restart if at end of prior buffered content or mediaPlayer was previously playing.
// NOTE: We test for < 1second of data because the media player can stop when there is still
// a few milliseconds of data left to play
int mediaLength = mediaPlayer.getDuration() - mediaPlayer.getCurrentPosition();
boolean atEndOfFile = mediaLength >= 1000;
Log.e(TAG, "MediaLen:"+mediaLength);
if (wasPlaying || atEndOfFile){
Log.e(TAG, "starting new player");
temp.release();
mediaPlayer.start();
}
}
});
// Lastly delete the previously playing buffered File as it's no longer needed.
oldBufferedFile.delete();
}catch (Exception e) {
Log.e(TAG, "Error updating to newly loaded content.", e);
}
}
Consider using exoplayer
http://developer.android.com/guide/topics/media/exoplayer.html
it was built with streaming in mind from the docs:
ExoPlayer supports features not currently provided by MediaPlayer, including Dynamic adaptive streaming over HTTP (DASH), SmoothStreaming, and persistent caching. ExoPlayer can be extended to handle additional media formats, and because you include it as part of your app code, you can update it along with your app.
I would like to use an arbitrary InputStream as a data source for a MediaPlayer object.
The reason for this is that the InputStream I am using is in fact an authorized HTTPS connection to a media resource on a remote server. Passing the URL in that case will obviously not work as an authentication is required. I can however do the authentication separately and get an InputStream to the resource - problem is what do I do once I have it?
I thought about the option of using a named pipe and passing its FileDescriptor to the setDataResource method of MediaPlayer. Is there a way to create named pipes in Android (without using NDK)?
Any other suggestion is most welcome.
I think I have found a solution. I would appreciate it if others who are interested would try this on their own and report the results with their device models and SDK version.
I have seen similar posts which direct to this but I thought I would post it anyway since it is newer and seems to work on newer versions of the SDK - so far it works on my Nexus One running Android 2.3.6.
The solution relies on bufferring the input stream to a local file (I have this file on the external storage but it will probably be possible to place it on the intenal storage as well) and providing that file's descriptor to the MediaPlayer instance.
The following runs in a doInBackground method of some AsyncTask that does AudioPlayback:
#Override
protected
Void doInBackground(LibraryItem... params)
{
...
MediaPlayer player = new MediaPlayer();
setListeners(player);
try {
_remoteStream = getMyInputStreamSomehow();
File tempFile = File.createTempFile(...);
tempFile.deleteOnExit();
_localInStream = new FileInputStream(tempFile);
_localOutStream = new FileOutputStream(tempFile);
int buffered = bufferMedia(
_remoteStream, _localOutStream, BUFFER_TARGET_SIZE // = 128KB for instance
);
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setDataSource(_localInStream.getFD());
player.prepareAsync();
int streamed = 0;
while (buffered >= 0) {
buffered = bufferMedia(
_remoteStream, _localOutStream, BUFFER_TARGET_SIZE
);
}
}
catch (Exception exception) {
// Handle errors as you see fit
}
return null;
}
The bufferMedia method buffers nBytes bytes or until the end of input is reached:
private
int bufferMedia(InputStream inStream, OutputStream outStream, int nBytes)
throws IOException
{
final int BUFFER_SIZE = 8 * (1 << 10);
byte[] buffer = new byte[BUFFER_SIZE]; // TODO: Do static allocation instead
int buffered = 0, read = -1;
while (buffered < nBytes) {
read = inStream.read(buffer);
if (read == -1) {
break;
}
outStream.write(buffer, 0, read);
outStream.flush();
buffered += read;
}
if (read == -1 && buffered == 0) {
return -1;
}
return buffered;
}
The setListeners method sets handlers for various MediaPlayer events. The most important one is the OnCompletionListener which
is invoked when playback is complete. In cases of buffer underrun (due to, say, temporary slow network connection) the player
will reach the end of the local file and transit to the PlaybackCompleted state. I identify those situations by comparing the
position of _localInStream against the size of the input stream. If the position is smaller, then playback is now really completed
and I reset the MediaPlayer:
private
void setListeners(MediaPlayer player)
{
// Set some other listeners as well
player.setOnSeekCompleteListener(
new MediaPlayer.OnSeekCompleteListener()
{
#Override
public
void onSeekComplete(MediaPlayer mp)
{
mp.start();
}
}
);
player.setOnCompletionListener(
new MediaPlayer.OnCompletionListener()
{
#Override
public
void onCompletion(MediaPlayer mp)
{
try {
long bytePosition = _localInStream.getChannel().position();
int timePosition = mp.getCurrentPosition();
int duration = mp.getDuration();
if (bytePosition < _track.size) {
mp.reset();
mp.setDataSource(_localInStream.getFD());
mp.prepare();
mp.seekTo(timePosition);
} else {
mp.release();
}
} catch (IOException exception) {
// Handle errors as you see fit
}
}
}
);
}
Another solution would be to start a proxy HTTP server on localhost. The media player will connect to this server with setDataSource(Context context, Uri uri). This solution works better than the previous and does not cause playback to glitch.