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.
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'm using Exoplayer to play a video in my application, what I want to do is change the speed of playback, for which Exoplayer provides a straightforward solution:
val playbackParameters = PlaybackParameters(whateverSpeedFloat)
exoPlayer.setPlaybackParameters(playbackParameters)
Now this works, but the problem I have is that the effect is not immediate, when you change the speed it takes a few frames for the actual speed to change. I guess it's because some of the frames are preloaded or buffered and the set playback parameters only affect the frames after this.
If I stop the video, and change speed from say 0.5x to 2x, then press play, it's very obvious that there is a delay in playback speed change. But, if I press stop, change the speed from 0.5x to 2x AND seek a different point in the video, and press play, it works great, there is no delay. I guess it reloads/buffers the new frames with the right playback parameters. I tried doing
exoPlayer.clearVideoDecoderOutputBufferRenderer()
after changing speeds to try and rebuffer the frames after setting the playback parameters but it doesn't seem to change anything.
Any ideas on how to fix this? Or other video player libraries that wouldn't have this problem?
UPDATE:
I still haven't found a solution, I took the problem to ExoPlayer and an issue was raised and "fixed"(https://github.com/google/ExoPlayer/issues/7982), however I'm still having the same problem, so atm I'll just get back to them and wait.
However, what was mentioned is that the delay is a know issue, and that right now there is no solution,
Correct. We looked at options to address the delay a while ago but couldn't find a clean/general/easy to implement approach (there is no API we can use to process the audio just before the mixer, so we have to do it upstream of the audio track buffer, which introduces latency).
Instead, they suggested initialising Exoplayer with a DefaultRenderersFactory with AudioTrackPlaybackParams set to true:
val defaultRenderersFactory =
DefaultRenderersFactory(this).setEnableAudioTrackPlaybackParams(true)
exoPlayer = SimpleExoPlayer.Builder(this, defaultRenderersFactory).build()
And this does in fact get rid of the delay (not 100% but I'd say around 80% which is good enough), but then the video speed gets all clunky and starts freezing and changing speeds every time it is paused/played or seeked to a different point.
I also tried modifying the buffering configuration #GensaGames suggested but even though I tested different configurations for a while, I never saw any change in the behaviour so discarded the solution and went to the exoPlayer repo.
I'll update this question when I finally have a working video speed changer.
I think you can decrease time of the buffering within configuration setup during ExpPlayer initializing. Below ex. on configuration, you can go throw documentation and check possible values.
/* Instantiate a DefaultLoadControl.Builder. */
DefaultLoadControl.Builder builder = new
DefaultLoadControl.Builder();
/* Maximum amount of media data to buffer (in milliseconds). */
final long loadControlMaxBufferMs = 60000;
/*Configure the DefaultLoadControl to use our setting for how many
Milliseconds of media data to buffer. */
builder.setBufferDurationsMs(
DefaultLoadcontrol.DEFAULT MIN BUFFER MS,
loadControlMaxBufferMs,
/* To reduce the startup time, also change the line below */
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS);
/* Build the actual DefaultLoadControl instance */
DefaultLoadControl loadControl = builder.createDefaultLoadControl();
/* Instantiate ExoPlayer with our configured DefaultLoadControl */
ExoPlayer player = ExoPlayerFactory.newSimpleInstance(
new DefaultRenderersFactory(this),
new DefaultTrackSelector(),
loadControl);
There is also good article about changing buffer time options.
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.
I'm using ExoPlayer 2.3.1 for playing the list of videos. I'm using code from sample:
MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
: new ConcatenatingMediaSource(mediaSources);
player.prepare(mediaSource, !haveResumePosition, false);
It's working fine, but I want to select video to play first.
For example I have 3 videos, when I clicks on second it should play and prev/next buttons in player should be available. Now it starts playing from the first mediaSource in array, and I can't find a way to set index of initial track or something like that.
Please check this out seekTo(windowIndex, positionMs). It says
Seeks to a position specified in milliseconds in the specified window.
Example:
player.seekTo(3, C.TIME_UNSET);
player.setPlayWhenReady(true);
I am working on an Android project that involves the use of Google's ExoPlayer.
I have a list of video sources which I build a playlist from using the following code:
for (int i = 0; i < vidList.length(); i++) {
MediaSource source = new ExtractorMediaSource(Uri.parse(vidList.getJSONObject(i).getString("url")),
buildDataSourceFactory(bandwidthMeter), extractorsFactory, mainHandler, HomeFragment.this);
mediaSources.add(source);
captions.add(vidList.getJSONObject(i).getString("caption"));
}
mediaSource = new ConcatenatingMediaSource(mediaSources.toArray(new MediaSource[mediaSources.size()]));
I then call
exoplayer.prepare(mediasource, false, false)
and the videos play in succession fine. I would like to display the caption of the currently playing video in a textView and so I have a separate list that holds the "caption" values for each video.
From scouring through the code I see that I can get the currently playing video in the playlist like this;
exoPlayer.getCurrentPeriodIndex()
Which seems to work and returns the index except for one problem. It returns the value of 0 twice as playback starts. That is video at index 0 returns period 0 as well as video at index 1. This only occurs at indexes 0 and 1 and thereafter everything else looks fine except that the getCurrentPeriodIndex() will return theAccurateIndex - 1.
I see this also happening in the demo Exoplayer application.
Is there a better way to determine what track is currently playing in the playlist?
Thanks.
To find the currently playing track, you need to reference currentWindowIndex exoPlayer field. Looks like this in Java...
exoPlayer.getCurrentWindowIndex()
I'm not sure what getCurrentPeriodIndex() does, and the docs don't elaborate, and I don't like speculating.
exoPlayer.getCurrentWindowIndex() is Deprecated.
Use exoPlayer.getCurrentMediaItemIndex() instead.