Prevent Exoplayer re-buffering for .mp4 videos - android

Within this project there is a 'SimpleExoPlayer' and the version of the player is 'exoplayer:r2.5.3'. After running the app 'SimpleExoPlayer' buffering the content of the video and playing smoothly. But the user set the 'SeekBar' to previous position, the 'SimpleExoPlayer' re-buffering to displaying the video. It is time consuming process for large file size of '.mp4' videos. Helping for solve this issue is kindly appreciated.
below is my code.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:padding="10dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:orientation="vertical">
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
android:id="#+id/simple_expo_player"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.google.android.exoplayer2.ui.SimpleExoPlayerView>
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private SimpleExoPlayerView simpleExoPlayerView;
SimpleExoPlayer player;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUi();
}
public void initUi(){
// 1. Create a default TrackSelector
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
// 2. Create the player
player = ExoPlayerFactory.newSimpleInstance(getApplicationContext(), trackSelector);
// 3. Produces DataSource instances through which media data is loaded.
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(), Util.getUserAgent(getApplicationContext(), "com.a3iteam.exoplayertest"));
// 4. Produces Extractor instances for parsing the media data.
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
// 5. This is the MediaSource representing the media to be played.
MediaSource videoSource = new ExtractorMediaSource(Uri.parse("https://www.w3schools.com/html/mov_bbb.mp4"),dataSourceFactory, extractorsFactory, null, null);
// 6. Prepare the player with the source.
simpleExoPlayerView = (SimpleExoPlayerView) findViewById(R.id.simple_expo_player);
simpleExoPlayerView.setPlayer(player);
}
}

There is not much which prevents the player from buffering when the user seeks backwards. Past data is discarded after playback and you can not easily change this behaviour.
There are options to minimize the latency when seeking backwards:
1. Make sure your server supports range requests
Support for range requests is IMO a must when serving video files and not only for backwards seeking but seeking in general.
It is time consuming process for large file size of '.mp4' videos.
The size of the mp4 file should not have an effect on the latency when seeking backwards. Your notion of 'large file size' make me think your server might not support http range requests. I may be wrong though. Just to make sure. :)
You can check like so:
curl -I http://i.imgur.com/z4d4kWk.jpg
HTTP/1.1 200 OK
...
Accept-Ranges: bytes
If you see the 'Accept-Ranges: bytes' header, range requests are supported.
2. Using a cache
The ExoPlayer library comes with a CacheDataSource and a corresponding CacheDataSourceFactory. Hence you can easily cache what's downloaded by wrapping your data source factory:
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(
getApplicationContext(), Util.getUserAgent(getApplicationContext(),
"com.a3iteam.exoplayertest"));
Cache cache = new SimpleCache(cacheDir,
new LeastRecentlyUsedCacheEvictor(maxBytes));
dataSourceFactory = new CacheDataSourceFactory(cache,
dataSourceFactory);
When a user seeks backwards, the the media is loaded from local disk instead of downloading which decreases latency. Not sure if you want to generally cache everything just for the backwards-seek use case. Maybe restrict it to mobile networks. On wifi buffering with http range request should be good enough.
Either way: delete cached data as quickly as possible.

I have discovered something about this caching mechanism. It looks like if you don't seek until the end of the video, the cache will be completed. Otherwise it caches again. I have verified this by completely cutting off the internet on my phone and emulator. It worked fine and i was able to seek anywhere in the video without internet connection. The key is wait for the whole video to finish before seeking. You can try the code below to create an exoplayer with cache.
private void prepare_player()
{
File downloadDirectory = context.getExternalFilesDir(null);
downloadDirectory = context.getFilesDir();
SimpleCache simple_cache = new SimpleCache(downloadDirectory, new NoOpCacheEvictor(), new ExoDatabaseProvider(getApplicationContext()));
MediaItem media_item = MediaItem.fromUri(video_uri);
CacheDataSource.Factory cache_datasource_factory = new CacheDataSource.Factory().setCache(simple_cache).setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory().setUserAgent("<your_app>"));
MediaSource media_source = new ProgressiveMediaSource.Factory(cache_datasource_factory).createMediaSource(media_item);
exo_player = new SimpleExoPlayer.Builder(this).build();
exo_player.setMediaSource(media_source);
exo_player.setPlayWhenReady(true);
exo_player.prepare();
player_view.setPlayer(exo_player);
}

We can specify backBufferDurationMs in LoadControl and use it to keep previous data buffered.
From the javaDoc it is: the duration of media to retain in the buffer prior to the current playback position, for fast backward seeking
val dataSource = AudioUtil.getMediaSourceFactory(this)
val loadControl = DefaultLoadControl.Builder()
.setBackBuffer(/* backBufferDurationMs= */ 1000 * 60 * 60, true) //for retaining previously loaded one hour data in buffer
.build()
player = ExoPlayer.Builder(this)
.setMediaSourceFactory(dataSource)
.setLoadControl(loadControl)
.build()

Related

Call execute key request takes too much time (exoplayer)

I am creating MP3 player using exoplayer.
I have online tracks with DRM(Widevine) protection so exoplayer call HttpMediaDrmCallback for get license key to play DRM protected content.
After set track to the exoplayer it's takes 5 second to calls executeKeyRequest from HttpMediaDrmCallback class, how can I decreased the time
Set tracks to the exoplayer
DefaultTrackSelector trackSelector = new DefaultTrackSelector(this);
ExoPlayer exoplayer = new ExoPlayer.Builder(this)
.setTrackSelector(trackSelector)
.build();
exoplayer.setMediaSources(mediaSources); // I already created this object before
exoplayer.prepare();
exoplayer.seekTo(0, C.TIME_UNSET);
exoplayer.setPlayWhenReady(true);

Exoplayer live streaming - efficient source switching

My android app navigates through a series of http live streams using remote control.
I use a TreeMap to store streams as tv channels, with channel number as key.
The code below is the function that open/changes source url.
I was wondering if you could suggest a more clean and efficient way to quickly switch to another source. Also possibly decrease load time of the next source.
private void playUrl(String url) {
Uri videoUri = Uri.parse(url);
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
try {
player.stop();
} catch (Exception e) {
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector);
PlayerView simpleExoPlayerView = findViewById(R.id.player_view);
////Set media controller
simpleExoPlayerView.setUseController(false);//set to true or false to see controllers
simpleExoPlayerView.requestFocus();
// Bind the player to the view.
simpleExoPlayerView.setPlayer(player);
}
// Measures bandwidth during playback. Can be null if not required.
// Produces DataSource instances through which media data is loaded.
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "CanaliTV2"), bandwidthMeter);
MediaSource videoSource = new HlsMediaSource(videoUri, dataSourceFactory, 1, null, null);
// Prepare the player with the source.
player.prepare(videoSource);
player.setPlayWhenReady(true); //run file/link when ready to play.
}
Its a difficult and complex subject, and one that I have been looking at myself recently.
When you call prepare on the player, its effectively restarting it. So any resources the player is currently using (could be renderers and codecs, drm sessions etc) are released and then recreated for the new stream. At which point the data source needs to fetch the data.
By continuing to call prepare for each channel, the only way really to improve start up time for playback after switching 'channel' is if the new data source is cached in some way in anticipation of the switch.
I think that in order to do that you'll need your own data source factory. The simplest way to write your own is to start by copying the source of the default into your own class and then begin to work out what changes you need to make in order to fulfill your requirement.
Now you can play around with LoadControl cache values to determine just how much data will be downloaded prior to playback and in doing so you'd try to minimise unnecessary data transfer, at the same time as begin playback as quick as possible. - about 2.5s is generally recommended (Exoplayers DefaultLoadControl provides this value out of the box), but you may be able to get away with less depending on how good the content cdn is.
But in actual fact, a better and more efficient way to do it, would be to not have to call prepare each time. And so yYou might be able to manipulate the players playlist by adding new MediaItem instances. see here for more details.
In theory, you could actually add all channels MediaItems to the same player, and then when a user changes the channel, you could seek to the start of the next MediaItem. This would then mean that the player won't need to re-instantiate all the required resources which may be more efficient for you.
This answer is my opinion and I may not be providing best practices, but as I said, I've investigated something very similar recently, with little success in any hard fact on how to complete my task. and so this was the rough outline of what i had come up with.
Hope it helps. (sorry its a bit wordy)

Play HLS radio without player view in my Android app

I am developing an android app to play the HLS radio. I don't want to have a player view since there is no video track for the streaming. I want to control the audio playing by a play/pause button.
I did some google search and I found that there is a google official project called ExoPlayer which seems to have a feature of playing hls. I tried the example but it shows that it does not support the audio codec. Therefore, I wonder if there is any solution of
an example of Exoplayer for playing HLS audio in android app without any player view
any other solution to achieve my goal
Thank you!
Edited on 11th, May
The following part is my code, though it seems to be a deprecated method
DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
String userAgent = Util.getUserAgent(MainActivity.this, "ExoPlayerDemo");
DefaultDataSourceFactory mediaDataSourceFactory = new DefaultDataSourceFactory(MainActivity.this, BANDWIDTH_METER,
new DefaultHttpDataSourceFactory(userAgent, BANDWIDTH_METER));
HlsMediaSource mediaSource = new HlsMediaSource.Factory(mediaDataSourceFactory)
.createMediaSource(Uri.parse(url), null, null);
TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
DefaultTrackSelector trackSelector = new DefaultTrackSelector(trackSelectionFactory);
DefaultRenderersFactory renderersFactory =
new DefaultRenderersFactory(MainActivity.this, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER);
ExoPlayer player = ExoPlayerFactory.newSimpleInstance(MainActivity.this, trackSelector);
player.setPlayWhenReady(true);
if(mediaSource!=null) {
player.prepare(mediaSource);
}
I will try using the example code from official site with a player ui and I will use a layout to overlap on the ui. I will be grateful if there is any other suggestion.

ExoPlayer stuttering with url from a specific site

So, my app plays radio stations from the Internet. And all is fine except of playing radio stations from 101.ru. Music is stuttering and I can't say that there are any equal time intervals between stutters.
I'm using ExoPlayer library v2.8.0.
What have I tried to fix it:
To update ExoPlayer to 2.10.3 - no result
To experiment with the buffer size of Exoplayer - no result or worst (few hours of experiments)
My code of ExoPlayer init:
IcyHttpDataSourceFactory factory = new IcyHttpDataSourceFactory.Builder(Util.getUserAgent(context, userAgent))
.setIcyHeadersListener(this)
.setIcyMetadataChangeListener(this).build();
dataSourceFactory = new DefaultDataSourceFactory(context, null, factory);
RenderersFactory renderersFactory = new DefaultRenderersFactory(context);
TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(new DefaultBandwidthMeter());
DefaultTrackSelector trackSelector = new DefaultTrackSelector(trackSelectionFactory);
LoadControl loadControl = new DefaultLoadControl();
player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector, loadControl);
player.addListener(this);
My code of ExoPlayer play from url:
player.prepare(generateMediaSource(Uri.parse(url)));
private MediaSource generateMediaSource(Uri uri) {
return new ExtractorMediaSource.Factory(dataSourceFactory)
.setExtractorsFactory(new DefaultExtractorsFactory())
.createMediaSource(uri);
}
So, 101.ru has about 80 radio stations and about 95% of them are stuttering. However, other 100-200 radio stations have an excellent quality.
What can I do to fix it? Thanks
The issue have been resolved. Problem was in IcyHttpDataSourceFactory. I've wrote in exoplayer github issues and there I've got an answer:
Version should be 2.10.3 and you shouldn't to use any other IcyHttpDataSourceFactory because they added ICY support in the latest release.
So, stuttering was resolved and sound is clear now.

How to get started with ExoPlayer

My requirement is very simple. I've a database in www.xyz.example.com. Using web service I pull the detail of a song/songs. I store them in an Arraylist.
The arraylist contains the url of the song. When user press play button, the url is passed to a service and song starts playing. I've successfully implemented android's default MediaPlayer. But I noticed that it takes too long to prepare. Then I found that Exoplayer is there to replace MediaPlayer. Now I notice that ExoPlayer is not as simple as Mediaplayer. It has so many classes, implementation etc. etc..
I added the latest player in my project using gradle
compile 'com.google.android.exoplayer:exoplayer:r2.0.0'
Now what should I do? How to actually intialize the Exoplayer? According to developer guide
// 1. Create a default TrackSelector
Handler mainHandler = new Handler();
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveVideoTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector =
new DefaultTrackSelector(mainHandler, videoTrackSelectionFactory);
I cannot get anything here. What is a BandwidthMeter here? What is TrackSelection here ? What is TrackSelector here ?
Again...
// 2. Create a default LoadControl
LoadControl loadControl = new DefaultLoadControl();
// 3. Create the player
SimpleExoPlayer player =
ExoPlayerFactory.newSimpleInstance(context, trackSelector, loadControl);
Here these are some another confusions. What is the difference between Exoplayer and SimpleExoPlayer ?
Moreover, I see that most of the projects using ExoPlayer in Github are about playing video over http. How can I achieve my above stated goal? (Please do not advice me to read the demo app. It is not simple.) Can anyone guide me to create a audio player.

Categories

Resources