While playing videos from a ConcatenatingMediaSource playlist, I would like the player to pause automatically at the start of a new item eg. not automatically playing it.
Using the demo application, I modified onPositionDiscontinuity function to detect current item change:
int currentPosition = 0;
#Override
public void onPositionDiscontinuity(#Player.DiscontinuityReason int reason) {
if (inErrorState) {
// This will only occur if the user has performed a seek whilst in the error state. Update
// the resume position so that if the user then retries, playback will resume from the
// position to which they seeked.
updateResumePosition();
}
if (player.getCurrentWindowIndex() != currentPosition) {
currentPosition = player.getCurrentWindowIndex();
player.setPlayWhenReady(false);
}
}
While this code pauses the player, it does not clear the surface view used by the player hence we are still seeing the last frame of the previous video. I suppose this callback is invoked too soon, but that's the only callback I found which was always invoked on playlist item change (onPlayerStateChanged might not be invoked).
How can I have the first frame of the newly current item displayed instead of the previous item last frame?
My weak workaround is to delay invocation of 200ms with Handler().postDelayed({ mPlayer?.playWhenReady = false }, 200).
This is not supported yet:
The problem with using the event listener is that there is no way to ensure the request to pause in your event listener
is handled while the first frame of the new playlist item is showing.
If stopping at approximately the right frame is fine, your solution of
pausing the player on position discontinuity looks fine, but I think
there's no guarantee that the video renderer will have kept up with
the player position which is why you still see a frame from the
previous source at the point of pausing the player. For what it's
worth, in my testing the video renderer did advance to the first frame
of the next playlist item before the request to pause was handled.
A couple of other suggestions:
You could try customizing your MediaSource to insert a position discontinuity at the start of each period. I think then you'd get
onRenderedFirstFrame at the start of each item. If you pause the
player there you can guarantee to pause the player at a frame in the
new playlist item.
To get this to work perfectly, in the sense that the first frame of the new playlist item is shown and the player pauses before any
other frame is shown, it will be necessary to coordinate showing a
frame and blocking further rendering (on the playback thread) with
pausing the player (on the thread your app is using to interact with
the payer). This will require a bit of code, but I think it is
probably possible roughly as follows: subclass MediaCodecVideoRenderer
and override onStreamChanged to get the time offset of the new
playlist item. Then override processOutputBuffer. In this method you
can detect when the first frame of the new stream has been rendered
(based on the time offset) and then prevent processing any output
frames until you've paused the player on your app's thread. After the
request to pause has been handled and the renderer is stopped
(onStopped) you can unblock processOutputBuffer.
We could also look at supporting this directly in the player, but it
will likely be a low priority at least for the moment.
Related
My goal is to pause the Current track right after it finishes, but the default behavior of playlist playback will not pause until the whole Playlist is finished.
I've tried using onPositionDiscontinuity() but it is called after the track has changed to the next one.
override fun onPositionDiscontinuity(reason: Int) {
super.onPositionDiscontinuity(reason)
if (reason == SimpleExoPlayer.DISCONTINUITY_REASON_PERIOD_TRANSITION) {
Log.v("xxx", "called!") //not called at the end of current track
}
}
And it seems like not supported natively (by official):
https://github.com/google/ExoPlayer/issues/3773
You can use the setPauseAtEndOfMediaItems method available on SimpleExoplayer.Builder like so:
player = SimpleExoPlayer.Builder(context)
.setPauseAtEndOfMediaItems(true)
.yourOtherOptions()
.build()
Unfortunately, there is no direct callback available to notify the end of the last frame of the current track. The only thing available with the ConcatenatingMediaSource, to know the end of a track is onPositionDiscontinuity(), but as you know that would be dispatched only after the first frame of the next track is already rendered. So in that case I think we can have the below possibilities wrt your use case:
Use VideoFrameMetadataListener interface and override the onVideoFrameAboutToBeRendered(), which would be called on the playback thread when a video frame is about to be rendered. In your case just before the next track rendering. link
Get the track duration [getDuration()] and keep getting the current playback position using getCurrentPosition() in every second(or any other time interval). And pause the playback when it returns the specified time. You can use a CountDownTimer for this and in the timer callback, onTick(), invoke getCurrentPosition() for the current track.
Use PlayerMessage to fire events at specified playback positions: The playback position at which it should be executed can be set using PlayerMessage.setPosition.link
Use onMediaItemTransition(): Called when playback transitions to another media item. Here generally we update the application’s UI for the new media item. So instead of updating the UI, we can pause the playback. Not sure if this gets called before or after onPositionDiscontinuity(). Feasibility needs to be verified.
I am trying add an intro video to my actual video. I am planning to achieve this by using ConcatenatingMediaSource. Below is the source code
DataSource.Factory dataSourceFactory = new CacheDataSourceFactory(VideoCache.getInstance(this), new DefaultDataSourceFactory(this, "test"));
MediaSource firstSource = new ExtractorMediaSource(Uri.parse("path1.mp4"),
dataSourceFactory, new DefaultExtractorsFactory(), null, null);
MediaSource secondSource = new ExtractorMediaSource(Uri.parse("path2.mp4"),
mediaDataSourceFactory, extractorsFactory, null, null);
// Plays the first video, then the second video.
ConcatenatingMediaSource concatenatedSource =
new ConcatenatingMediaSource(firstSource, secondSource);
player.prepare(concatenatedSource);
I need to know when the intro video stops playing so I can make some UI changes as well as start showing the controller layout for the video. One way I have tried, is to set a CountDownTimer with a hardcoded value which does the necessary changes once onFinish is called. I was wondering if there are any listeners which will help me get a callback for when a source ends. Is onTracksChanged a proper callback to consider?
In your case, both onTrackChanged() and onPositionDiscontinuity() callbacks will be called when the second video is start to play.
onPositionDiscontinuity() will also be invoked during a seek operation. You can get the newly changed window index by calling player.getCurrentWindowIndex() inside it. On the other hand, calling player.getCurrentWindowIndex() inside onTrackChanged() will not be guaranteed to get the right index.
UPDATE:
there is a section in the documentation explained how to detect playback transitions.
There are three types of events that may be called when the current playback item changes:
EventListener.onPositionDiscontinuity with reason = Player.DISCONTINUITY_REASON_PERIOD_TRANSITION.
This happens when playback automatically transitions from one item to the next.
EventListener.onPositionDiscontinuity with reason = Player.DISCONTINUITY_REASON_SEEK.
This happens when the current playback item changes as part of a seek operation, for example when calling Player.next.
EventListener.onTimelineChanged with reason = Player.TIMELINE_CHANGE_REASON_DYNAMIC.
This happens when the playlist changes, e.g. if items are added, moved, or removed.
In all cases, when your application code receives the event, you can query the player to determine which item in the playlist is now being played. This can be done using methods such as Player.getCurrentWindowIndex and Player.getCurrentTag. If you only want to detect playlist item changes, then it’s necessary to compare against the last known window index or tag, because the mentioned events may be triggered for other reasons.
We are trying play audio from url (m3u8 file). Media player starts fine no issues. Audio also plays cleanly. Issue starts when do seek in the player. Behavior very strange. it seeks to the proper position then starts playing audio. After while it seeks again like couples seconds (better word is skips some seconds since it jumps to the position directly) and can be observed in the media playback time counter, again plays for a while again jumps some seconds and this continues till end of the media.
We have our custom seek bar which is nothing but a progressbar, and when we do seek the progress bar we send same seek position to media player by calling onseek() method.
Note: Issue Happens only Lollipop nexus devices(tablet and phone).
Strange observation jump happens only if the time counters last position 9
(i.e if mediaplayed 12:29[mm:ss] then will jump to some other random place 12:3X[mm:ss],again mediaplayed 12:39[mm:ss] then will jump to some other random place 12:4X [mm:ss] )
Why is it happening?
You should pass the outer manifest to the player. This should resolve your issue.
When using MediaPlayer, I noticed that whenever my phone stucks, the MediaPlayer glitches and then continues playing from the position in the audio it glitched.
This is bad for my implementation since I want the audio to be played at a specific time.
If I have a song of 1000 millisecond length, I want is the ability to set MediaPlayer to start playing at some specific time t, and then exactly stop at at time t+1000.
This means that I actually need two things:
1) Start MediaPlayer at a specific time with a very small delay.
2) Making MediaPlayer glitches ignore the audio they glitched on and continue playing in order to finish the song on time.
The delay of the functions is very important to me and I need the audio to be played exactly(~) at the time it was supposed to be played.
Thanks!
You will need to use possibly mp.getDuration(); and/or mp.getCurrentPosition(); although it's impossible to know exactly what you mean by "I need the audio to be played exactly(~) at the time it was supposed to be played."
Something like this should get you started:
int a = (mp.getCurrentPosition() + b);
Thanks for the answer Mike. but unfortunately this won't help me. Let's say that I asked MediaPlayer to start playing a song of length 3:45 at 00:00. At 01:00 I started using the phone's resources, due to the heavy usage my phone glitched making MediaPlayer pause for 2 seconds.
Time:
00:00-01:00 - I heard the audio at 00:00-01:00
01:00-01:02 - I heard silence because the phone glitched
01:02-03:47 - I heard the audio at 01:00-03:45 with 2 second time skew
Now from what I understood MediaPlayer is a bad choice of usage on this problem domain, since MediaPlayer provides a high level API.I am currently experimenting with the
AudioTrack class which should provide me with what I need:
//Creating a new audio track
AudioTrack audioTrack = new AudioTrack(...)
//Get start time
long start = System.currentTimeMillis();
// loop until finished
for (...) {
// Get time in song
long now = System.currentTimeMillis();
long nowInSong = now - start;
// get a buffer from the song at time nowInSong with a length of 1 second
byte[] b = getAudioBuffer(nowInSong);
// play 1 second of music
audioTrack.write(b, 0, b.length);
// remove any unplayed data
audioTrack.flush();
}
Now if I glitch I only glitch for 1 second and then I correct myself by playing the right audio at the right time!
NOTE
I haven't tested this code but it seems like the right way to do it. If it will actually work I will update this post again.
P.S. seeking in MediaPlayer is:
1. A heavy operation that will surely delay my music (every millisecond counts here)
2. Is not thread safe and cannot be used from multiple threads (seeks, starts etc...)
I am creating a program which requires me to change a setting in the program when the video reaches specific points (at 1/3 of the video's completion, 2/3's and on completion). Android has a built in callback method for completion so performing an action at that point in time is not difficult. However, I don't know how to go about checking when the video has reached 1/3 and 2/3's of completion.
Using a MediaPlayer control you will get
the total duration of your media file in milliseconds:
myMediaPlayer.getDuration()
you will implement a thread that check every second for the current position at 1/3, 2/3 and 3/3 of the videos completion, with
myMediaPlayer.getCurrentPosition(); //***current Position in milliseconds.