Exoplayer live streaming - efficient source switching - android

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)

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);

seekTo(mTimeMilis) won't work properly when using ConcatenatingMediaSource in ExoPlayer

I have more than one video to play one by one. After Creating MediaSource of each video, All these are going to ConcatenatingMediaSource(mediaSources[]). Normally it play one by one. But when video is fast forward using seekTo(), 1st video is ok but other videos do not follow seekTo().
Suppose 1st video is 10s, 2nd 12s, 3rd 10s.
If I call seekTo((long)12*1000) it should play 2nd video with 2s forward. But it plays from the beginning of 2nd video.
Setting VideoSources
DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(),Util.getUserAgent(getApplicationContext(), "ExoPlayer"));
MediaSource mediaSource = new ExtractorMediaSource.Factory(defaultDataSourceFactory).createMediaSource(videoItem.getVideoUri());
videoItemArrayList.get(k).setVideoSource(mediaSource);
Concatenate Sources
MediaSource[] mediaSources = new MediaSource[videoItemArrayList.size()];
int j=0;
for(VideoItem item : videoItemArrayList){
mediaSources[j] = item.getVideoSource();
++j;
}
concatenatedSource = new ConcatenatingMediaSource(mediaSources);
Setup exoplayer
exoPlayer.prepare(concatenatedSource);
exoPlayer.seekTo(0);
exoPlayer.setPlayWhenReady(true);
exoPlayer.getPlaybackState();
Using exoplayer.seekTo(period) internally calls currentWindowIndex() internally of the source. While you are playing the first video in the concatenated mediasource you end up receiving windowIndex as 0. Use seekTo(windowIndex, time) to solve the issue.

ExoPlayer Cant Play some audio streams

i am newbie here. i am developing a android app to listen to online radio stations.
problem i am getting here is ExoPlayer(2.9.2) can or cant play some streams of same type of different bitrates.
For Example
These mpga streams
http://108.61.34.50:7130/ ---> 96kbs ===> Playable
http://s3.voscast.com:8408 ---> 64kbs ===> Not Playable
http://220.247.227.20:8000/citystream ---> 128kbs ===> Not Playable
i used vlc codec window to get this information, and those 3 streams share same values other than bitrate.
i am using ExtractorMediaSource
songs.add(Uri.parse("http://220.247.227.20:8000/citystream"));//City
player= ExoPlayerFactory.newSimpleInstance(this,
new DefaultTrackSelector());
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
Util.getUserAgent(this, "AudioApp"));
ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource();
for (Uri temp : songs)
{
BaseMediaSource mediaSourse=
new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(temp);
concatenatingMediaSource.addMediaSource(mediaSourse);
}
player.prepare(concatenatingMediaSource);
player.setPlayWhenReady(true);
Hope anyone can help me out..
After testing various different streams, i realized that this is a device specific error.
Huawei Y5 2017
the other phone's that i tested work pretty well and i ended up submiting issue to ExpoPlayer team on github without knowing this is device specific issue.
Here it is
Why does ExoPlayer cant play some of the streams of same type with different bitrates ?

An AAC audio stream is playable in VLC for Android, but not in Exoplayer

I have an RTMP stream I want to play in my app using the Exoplayer library. My setup for that is as follows:
TrackSelector trackSelector = new DefaultTrackSelector();
RtmpDataSourceFactory rtmpDataSourceFactory = new RtmpDataSourceFactory(bandwidthMeter);
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
factory = new ExtractorMediaSource.Factory(rtmpDataSourceFactory);
factory.setExtractorsFactory(extractorsFactory);
createSource();
mPlayer = ExoPlayerFactory.newSimpleInstance(mActivity, trackSelector, new DefaultLoadControl(
new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
1000, // min buffer
3000, // max buffer
1000, // playback
2000, //playback after rebuffer
DefaultLoadControl.DEFAULT_TARGET_BUFFER_BYTES,
true
));
vwExoPlayer.setPlayer(mPlayer);
mPlayer.addListener(mVideoStreamHandler);
mPlayer.addVideoListener(new VideoListener() {
#Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
Log.d("hasil", "onVideoSizeChanged: w:" + width + ", h:" + height);
String res = width + "x" + height;
resolution.setText(res);
}
#Override
public void onRenderedFirstFrame() {
}
});
Where createSource() is as follows:
private void createSource() {
mMediaSource180 = factory.createMediaSource(Uri.parse(API.GAME_VIDEO_STREAM_URL_180));
mMediaSource360 = factory.createMediaSource(Uri.parse(API.GAME_VIDEO_STREAM_URL_360));
mMediaSource720 = factory.createMediaSource(Uri.parse(API.GAME_VIDEO_STREAM_URL_720));
mMediaSourceAudio = factory.createMediaSource(Uri.parse(API.GAME_AUDIO_STREAM_URL));
}
My current problem is that only the first three ExtractorMediaSources work fine in Exoplayer. The mMediaSourceAudio refuses to play in Exoplayer, but works just fine in the VLC Media Player for Android.
Right now I have a suspicion that the format is AAC-LTP, or whatever AAC variant that requires a codec available in VLC but not in default Android. However, I do not have access to the encoding process so I don't know for sure.
If this isn't the case, what is it?
EDIT:
I've been debugging the BandwidthMeter and added a MediaSourceEventListener. When I use the normal Video sources, onDownstreamFormatChanged() gets called, but not when I use that Audio Stream source.
In addition, the BandwidthMeter works fine, with bytes always downloaded in all parts of the stream and more bytes when the video stream comes in, but only in the Audio only stream that, when I call mPlayer.getBufferedPosition(), the returned value is always 0. Also, when I use the Audio Stream source, no OMX code was called - no decoders were set up.
Am I seeing a malformed audio stream, or do I need to change my Exoplayer's settings?
EDIT 2:
Further debugging reveals that, in all the Video streams and Audio stream, the same FlvExtractor is used. Even though the Video streams have the avc video track encoding and mp4a-latm audio track encoding. Is this normal?
Turns out it's because the stream was recognized to have two tracks/sampleQueues. One Audio track, and one track with null format. That null track was supposed to be the video track, which was supposed to exist according to the stream's flvHeader flag.
For now, I get around this by creating a custom MediaSource using a custom MediaPeriod. Said custom MediaPeriod having code to separate the video and audio tracks of the SampleQueues, then using the audio-only SampleQueue[] instead of the source SampleQueue[] when I want to play the audio-only stream.
Though this gives me another point of concern: There's something one can do to alter the 'has audio track (flag & 0x04) and video track (flag & 0x01)' flag in the rtmp stream, right?
Thanks for the comments, I'm new to ExoPlayer. But your comments helped me in debugging and getting multiple workarounds to the issue.
I tried to use custom MediaSource and custom MediaPeriod to address this audio issue. I have observed video format data coming after audio data incase of video+audio wowza stream, so the function maybeFinishPrepare() will wait for getting both video and audio format tag data before invoking onPrepared, incase if video tagData is received first. Incase of audio data received first, it wont wait and will call onPrepare().
With the above changes, I was able to play audio alone and video_audio wowza streams, where rtmp tagHeader with tagTypes were coming in the order of video tagData and then followed by audio data.
I wasn't able to use the same patch with srs server to play both audio_only and video_audio streams with the same changes. srs server is giving tagData in the order of audio and then video tagData,
So, I debugged further in FlvExtractor. In readFlvHeader, I have overriden the hasAudio and hasVideo variables. These variables will be set based on the first few tagHeaders(5 or 6). I used peekFully on input for 6 times in a loop. In each loop after fetching tagType and tagDataSize, tagDataSize is used to input.advancePeekPosition(), and tagType is used to identify whether we have audio/video format data in tagData. After peeking for first 6 consecutive tagHeaders, I was able to get actual values of hasAudio and hasVideo, and ignored the flvHeaders.flags, which were used to set these variables.
Custom FlvExtractor workaround, looked cleaner than custom MediaSource/MediaPeriod, as we will create those many tracks as necessary, as we are setting proper hasVideo/hasAudio values.

Prevent Exoplayer re-buffering for .mp4 videos

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()

Categories

Resources