Exoplayer changing subtitle on the fly - android

I am trying to change a subtitle for a video in Exoplayer. Video can already have a subtitle or user can add a new subtitle from internal storage. I know that before starting the player we can create Mediasource for both subtitle and video and merge using MergingMediaSource. However, I am not sure how to replace/add new subtitles to the currently playing video? Is it possible using DynamicConcatenatingMediaSource or any other way to do so?
MediaSource[] subTitleMediaSources = new MediaSource[uris.length];
Format subtitleFormat = Format.createTextSampleFormat(
null,
MimeTypes.APPLICATION_SUBRIP,
C.SELECTION_FLAG_DEFAULT,
null);
for (int i = 0; i < uris.length; i++) {
String subTitle = getSubtitleFile(subs, uris[i]);
if (subTitle != null) {
subTitleMediaSources[i] = new SingleSampleMediaSource.Factory(mediaDataSourceFactory).
createMediaSource(Uri.parse(subTitle), subtitleFormat,
C.TIME_UNSET);
} else {
subTitleMediaSources[i] = new SingleSampleMediaSource.Factory(mediaDataSourceFactory).
createMediaSource(Uri.parse("dummy"), subtitleFormat, C.TIME_UNSET);
}
}
MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
: new ConcatenatingMediaSource(mediaSources);
MediaSource subsMediaSource = subTitleMediaSources.length == 1 ? subTitleMediaSources[0]
: new ConcatenatingMediaSource(subTitleMediaSources);
MediaSource mergedSource;
if (subsMediaSource == null) {
mergedSource = mediaSource;
} else {
mergedSource = new MergingMediaSource(mediaSource, subsMediaSource);
}

I was able to get this to work by removing the Mediasource and adding it again. I used a ConcatenatingMediaSource. I think there should be a better/easy way than this by just adding new subtitle source to existing MergingMediasource. Suggestions are most welcome.
public void onSubSelected(String path) {
final long position = player.getCurrentPosition();
if (path == null) {
return;
}
List<MediaSource> subtitleSource = new ArrayList<>();
Uri uri = uris[currentIndex];
MediaSource trackSource = buildMediaSource(uri);
List<String> subPaths;
if (!pathSubtitleMapping.containsKey(uri)) {
subPaths = new ArrayList<>();
subPaths.add(path);
subtitleSource.add(buildSubtitleSource(path));
pathSubtitleMapping.put(uris[currentIndex], subPaths);
} else {
subPaths = pathSubtitleMapping.get(uri);
if (!subPaths.contains(path)) {
subPaths.add(path);
}
for (String path1 : subPaths) {
subtitleSource.add(buildSubtitleSource(path1));
}
}
MediaSource mediaSources[] = new MediaSource[subtitleSource.size() + 1];
mediaSources[0] = trackSource;
int index = 1;
for (MediaSource source : subtitleSource) {
mediaSources[index] = source;
index++;
}
finalMediaSource.removeMediaSource(currentIndex);
finalMediaSource.addMediaSource(currentIndex, new MergingMediaSource(mediaSources), new Runnable() {
#Override
public void run() {
player.seekTo(currentIndex, position);
}
});
}
private Map<Uri, List<String>> pathSubtitleMapping = new HashMap<>(); // (uri, list of subs)
private MediaSource buildMediaSource(Uri uri) {
MediaSource newSource = new ExtractorMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri);
return newSource;
}
private MediaSource buildSubtitleSource(String path) {
Format subtitleFormat = Format.createTextSampleFormat(
null,
MimeTypes.APPLICATION_SUBRIP,
C.SELECTION_FLAG_DEFAULT,
null);
MediaSource mediaSource = new SingleSampleMediaSource.Factory(mediaDataSourceFactory).
createMediaSource(Uri.parse(path), subtitleFormat, C.TIME_UNSET);
return mediaSource;
}

Related

How to initialise exoplayer to play dash(.mpd) videos

I have the playlist of dash videos and this was the code I used to play the media when the version was 2.7.3. Now I have updated to 2.9.5, I'm facing issues.
My init and mediasource methods :
private void initializePlayer() {
if (player == null) {
// a factory to create an AdaptiveVideoTrackSelection
TrackSelection.Factory adaptiveTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
player = ExoPlayerFactory.newSimpleInstance(
new DefaultRenderersFactory(this),
new DefaultTrackSelector(adaptiveTrackSelectionFactory),
new DefaultLoadControl());
player = ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(this),
new DefaultTrackSelector(), new DefaultLoadControl());
playerView.setPlayer(player);
player.addListener(new PlayerEventListener());
/*player.setPlayWhenReady(playWhenReady);
player.seekTo(currentWindow, playbackPosition);*/
}
playListMediaSources = buildPlayListMediaSource(serialURLs);
concatenatingMediaSource = new ConcatenatingMediaSource(playListMediaSources);
player.prepare(concatenatingMediaSource, true, false);
player.setPlayWhenReady(playWhenReady);
player.seekTo(currentWindow, playbackPosition);
}
private MediaSource buildMediaSource(Uri uri) {
/* DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
DefaultHttpDataSourceFactory defaultHttpDataSourceFactory = new DefaultHttpDataSourceFactory("user-agent");*/
DataSource.Factory manifestDataSourceFactory =
new DefaultHttpDataSourceFactory("ua");
DashChunkSource.Factory dashChunkSourceFactory =
new DefaultDashChunkSource.Factory(
new DefaultHttpDataSourceFactory("ua", BANDWIDTH_METER));
return new DashMediaSource.Factory(dashChunkSourceFactory,
manifestDataSourceFactory).createMediaSource(uri);
}
private MediaSource[] buildPlayListMediaSource(String[] serialURLs) {
MediaSource[] mediaSources = new MediaSource[serialURLs.length];
Uri uri;
for (int i = 0; i < serialURLs.length; i++) {
uri = Uri.parse(serialURLs[i]);
mediaSources[i] = buildMediaSource(uri);
}
return mediaSources;
}
I modified my init code to this :
private void initializePlayer() {
if (player == null) {
TrackSelection.Factory adaptiveTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
player = ExoPlayerFactory.newSimpleInstance(this,
new DefaultRenderersFactory(this),
new DefaultTrackSelector(adaptiveTrackSelectionFactory),
new DefaultLoadControl());
player = ExoPlayerFactory.newSimpleInstance(this,new DefaultRenderersFactory(this),
new DefaultTrackSelector(), new DefaultLoadControl());
}
if (serialURLs != null) {
playListMediaSources = buildPlayListMediaSource(serialURLs);
concatenatingMediaSource = new ConcatenatingMediaSource(playListMediaSources);
player.prepare(concatenatingMediaSource, true, false);
player.setPlayWhenReady(playWhenReady);
player.seekTo(currentWindow, playbackPosition);
}
}
But failed to play the video. My guess is I have to make some changes in Media sources methods as well, but I'm unable to figure out the changes. With my current code, the log shows the following error :
E/AndroidRuntime: FATAL EXCEPTION: ExoPlayerImplInternal:Handler
Process: com.packagename, PID: 2731
java.lang.AbstractMethodError: abstract method "void com.google.android.exoplayer2.source.BaseMediaSource.prepareSourceInternal(com.google.android.exoplayer2.upstream.TransferListener)"
at com.google.android.exoplayer2.source.BaseMediaSource.prepareSource(BaseMediaSource.java:140)
at com.google.android.exoplayer2.ExoPlayerImplInternal.prepareInternal(ExoPlayerImplInternal.java:398)
at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:285)
at android.os.Handler.dispatchMessage(Handler.java:101)
at android.os.Looper.loop(Looper.java:164)
at android.os.HandlerThread.run(HandlerThread.java:65)
I'm adding this text to avoid mostly code error. Any help is appreciated. Thanks in advance.
Both com.google.android.exoplayer:exoplayer-core and com.google.android.exoplayer:exoplayer-dash needs to be the same version in your build.gradle.
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.6'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.9.6'
Finally i settled for this:
private void initializePlayer() {
if (player == null) {
TrackSelection.Factory adaptiveTrackSelection = new AdaptiveTrackSelection.Factory();
TrackSelector trackSelector = new DefaultTrackSelector(adaptiveTrackSelection);
DefaultLoadControl loadControl = new DefaultLoadControl.Builder().setBufferDurationsMs(64*1024, 128*1024, 1024, 1024).createDefaultLoadControl();
//LoadControl loadControl = new DefaultLoadControl();
// HttpDataSource.Factory factory = new DefaultHttpDataSourceFactory(Util.getUserAgent(this, "Exo2"));
player = ExoPlayerFactory.newSimpleInstance(this,new DefaultRenderersFactory(this), trackSelector, loadControl);
playerView.setPlayer(player);
player.addListener(new PlayerEventListener());
}
if (isPlaylist) {
if (serialURLs != null) {
nextBtn.setVisibility(View.VISIBLE);
playListMediaSources = buildPlayListMediaSource(serialURLs);
concatenatingMediaSource = new ConcatenatingMediaSource(playListMediaSources);
readLastSeen(contentName, isWebSeries);
player.prepare(concatenatingMediaSource, true, false);
player.setPlayWhenReady(playWhenReady);
player.seekTo(currentWindow, playbackPosition);
} else {
// previousBtn.setVisibility(View.GONE);
//nextBtn.setVisibility(View.GONE);
Uri uri = Uri.parse(getString(R.string.sample_video));
mediaSource = buildMediaSource(uri);
Toast.makeText(MediaPlayerActivity.this, "Playing sample video..", Toast.LENGTH_LONG).show();
player.prepare(mediaSource, true, false);
player.setPlayWhenReady(playWhenReady);
player.seekTo(playbackPosition);
}
} else {
if (videoURL != null) {
Uri uri = Uri.parse(videoURL);
mediaSource = buildMediaSource(uri);
}
player.prepare(mediaSource, true, false);
player.setPlayWhenReady(playWhenReady);
player.seekTo(playbackPosition);
}
}
private MediaSource buildMediaSource(Uri uri) {
DataSource.Factory dataSourceFactory =
new DefaultDataSourceFactory(MediaPlayerActivity.this, "ua");
return new DashMediaSource.Factory(dataSourceFactory)
.createMediaSource(uri);
}
and these are my dependencies :
implementation 'com.google.android.exoplayer:exoplayer-core:2.10.0'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.10.0'
implementation 'com.google.android.exoplayer:extension-mediasession:2.8.4'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.8.+'
implementation 'com.google.android.exoplayer:exoplayer:r2.4.0'

Sideload subtitle not working in ExoPlayer

I am trying to sideload a subtitle file with my video, but it does not work.
Code:
private MediaSource buildMediaSourceWithSubtitle(Uri uri, #Nullable String overrideExtension, Uri subtitle) {
Format subtitleFormat = Format.createTextSampleFormat(null, MimeTypes.APPLICATION_SUBRIP, 0, null);
MediaSource subtitleSource = new SingleSampleMediaSource.Factory(dataSourceFactory).createMediaSource(subtitle, subtitleFormat, C.TIME_UNSET);
MediaSource mediaSource = null;
#ContentType int type = Util.inferContentType(uri, overrideExtension);
switch (type) {
case C.TYPE_DASH:
mediaSource = new DashMediaSource.Factory(dataSourceFactory).setManifestParser(
new FilteringManifestParser < >(new DashManifestParser(), getOfflineStreamKeys(uri))).createMediaSource(uri);
return new MergingMediaSource(mediaSource, subtitleSource);
case C.TYPE_SS:
mediaSource = new SsMediaSource.Factory(dataSourceFactory).setManifestParser(
new FilteringManifestParser < >(new SsManifestParser(), getOfflineStreamKeys(uri))).createMediaSource(uri);
return new MergingMediaSource(mediaSource, subtitleSource);
case C.TYPE_HLS:
mediaSource = new HlsMediaSource.Factory(dataSourceFactory).setPlaylistParserFactory(
new DefaultHlsPlaylistParserFactory(getOfflineStreamKeys(uri))).createMediaSource(uri);
return new MergingMediaSource(mediaSource, subtitleSource);
case C.TYPE_OTHER:
mediaSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
return new MergingMediaSource(mediaSource, subtitleSource);
default:
{
throw new IllegalStateException("Unsupported type: " + type);
}
}
}
MediaSource videoSource = new ExtractorMediaSource(
videoUri,
dataSourceFactory,
new DefaultExtractorsFactory(),
null,
null);
Format subtitleFormat = Format.createTextSampleFormat(
null,
MimeTypes.APPLICATION_SUBRIP,
C.SelectionFlags,
"en");
MediaSource textMediaSource = new SingleSampleMediaSource(
Uri.parse("http://www.storiesinflight.com/js_videosub/jellies.srt"),
dataSourceFactory,
subtitleFormat,
C.TIME_UNSET);
source = new MergingMediaSource(videoSource, textMediaSource); // to be used later
// nested to some class that has SubtitleView as member
class SomeListener implements ..., TextRenderer.Output, ... {
...
// this function doesn't seem to be invoked
public void onCues(List cues) {
// some logging here
if (subtitleView != null) {
subtitleView.onCues(cues);
}
}
...
}
player.setTextOutput(listener);
...
// later, play the video assuming its prepared and whatnot
player.setPlayWhenReady(true);
In Exoplayer 2.16.1 I used this code and worked properly:
val subtitle = MediaItem.SubtitleConfiguration.Builder(srtUri)
.setMimeType(MimeTypes.APPLICATION_SUBRIP)
.setLanguage("en")
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
.build()
val mediaItem = MediaItem.Builder()
.setUri(videoUrl)
.setSubtitleConfigurations(ImmutableList.of(subtitle))
.build()
player?.setMediaItem(mediaItem)

Exoplayer's DynamicConcatenatingMediaSource not working

I am implementing a music application. I am using Exoplayer2
I am trying to use DynamicConcatenatingMediaSource to add & remove items to the playlist dynamically.
Here is my implementation.
public void initMusicPlayer(){
if (songs !=null)
{
MediaSource mediaSource;
ArrayList<MediaSource> sources = new ArrayList<>();
MusicItem song;
for (int i=0;i< songs.size();i++)
{
song = songs.get(i);
mediaSource = buildMediaSource(Uri.parse(song.getMusicUrl()));
sources.add(mediaSource);
}
dynamicMediaSource = new DynamicConcatenatingMediaSource();
dynamicMediaSource.addMediaSources(sources);
exoPlayer.prepare(dynamicMediaSource,false,false);
exoPlayer.addListener(this);
if (currentPlayingSongIndex == -1)
{
exoPlayer.setPlayWhenReady(false);
}
else
{
exoPlayer.seekTo(currentPlayingSongIndex,0);
exoPlayer.setPlayWhenReady(true);
}
}
}
public void addItemToPlaylist(MusicItem song,boolean shouldPlay){
long position = exoPlayer.getContentPosition();
MediaSource mediaSource = buildMediaSource(Uri.parse(song.getMusicUrl()));
dynamicMediaSource.addMediaSource(mediaSource);
exoPlayer.prepare(dynamicMediaSource,false,false);
if (shouldPlay)
{
exoPlayer.seekTo(currentPlayingSongIndex,0);
}
else
{
exoPlayer.seekTo(currentPlayingSongIndex,position);
}
}
But, this implementation is not working. It doesn't play anything.
What's wrong in the above code ?
Also, how to add items dynamically to playlist ? Will the above addItemToPlaylist work ?
This is the function I created to play a playlist of mp3 files in Kotlin.
(You can copy paste it in Android Studio and it will be converted to Java if you use Java)
private fun mediaPlayerConfiguration() {
val bandwidthMeter = DefaultBandwidthMeter()
val extractorsFactory = DefaultExtractorsFactory()
val trackSelectionFactory = AdaptiveTrackSelection.Factory(bandwidthMeter)
val trackSelector = DefaultTrackSelector(trackSelectionFactory)
val defaultBandwidthMeter = DefaultBandwidthMeter()
val dataSourceFactory = DefaultDataSourceFactory(this,
Util.getUserAgent(this, "mediaPlayerSample"), defaultBandwidthMeter)
val dynamicConcatenatingMediaSource = DynamicConcatenatingMediaSource()
for (item in myActivity.list) {
var mediaSource = ExtractorMediaSource.Factory(dataSourceFactory).setExtractorsFactory(extractorsFactory).createMediaSource(Uri.parse(item))
dynamicConcatenatingMediaSource.addMediaSource(mediaSource)
}
mPlayer = ExoPlayerFactory.newSimpleInstance(this, trackSelector)
mPlayer.prepare(dynamicConcatenatingMediaSource)
mPlayer.playWhenReady = true
}

Refresh Media source in exoplayer

I am using Exo Player ExtractorMediaSource for playing video in my android app. I am downloading media from server and save in local database and on a specific time Alarm i play this media using ConcatenatingMediaSource in exo player. but first i check that all video file downloaded or not and start player with downloaded media source . and if any video is not downloaded then i want to download it in background at when it downloaded then i want to add this video in my already created playlist
This is sample code
private void playAndUpdateVideo(ArrayList<String> mediaSourc) {
simpleExoPlayerView.setVisibility(View.VISIBLE);
simpleExoPlayerView.setDefaultArtwork(null);
mainHandler = new Handler();
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveVideoTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector = new DefaultTrackSelector( videoTrackSelectionFactory);
dataSourceFactory = new DefaultDataSourceFactory(context,
Util.getUserAgent(context, "com.cloveritservices.hype"), bandwidthMeter);
// 2. Create a default LoadControl
extractorsFactory = new DefaultExtractorsFactory();
LoadControl loadControl = new DefaultLoadControl();
// 3. Create the player
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, loadControl);
player.addListener(this);
//Set media controller
simpleExoPlayerView.setUseController(false);
simpleExoPlayerView.requestFocus();
// Bind the player to the view.
simpleExoPlayerView.setPlayer(player);
MediaSource[] mediaSources = new MediaSource[mediaSourc.size()];
for (int i=0;i<mediaSourc.size();i++)
{
mediaSources[i]= buildMediaSource(Uri.parse(mediaSourc.get(i)));
}
MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
: new ConcatenatingMediaSource(mediaSources);
LoopingMediaSource loopingSource = new LoopingMediaSource(mediaSource);
player.prepare(loopingSource);
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
boolean isChecked = settings.getBoolean("switch", false);
if (!isChecked)
player.setVolume(0f);
else player.setVolume(2f);
player.setPlayWhenReady(true);
}
And here i am checking for video file that it is downloaded or not
if (CommonUtils.isExternalStorageExistAndWritable()) {
for (int i = 0; i < videoUrl.size(); i++) {
if (!new File(Environment.getExternalStorageDirectory().toString() + Constants.PROFILE_VIDEO_FOLDER + CommonUtils.fileFromUrl(videoUrl.get(i))).exists() && !CommonUtils.currentlyDownloading(context,CommonUtils.fileFromUrl(videoUrl.get(i)))) {
downloadByDownloadManager(videoUrl.get(i), CommonUtils.fileFromUrl(videoUrl.get(i)));
if (flag==Constants.FLAG_PLAY){downloadFlag=true;}
}
}
} else {
Toast.makeText(getApplicationContext(), "SD Card not mounted.Please Mount SD Card", Toast.LENGTH_SHORT).show();
}
if (flag==Constants.FLAG_PLAY && !downloadFlag)
{
playAndUpdateVideo(videoUrl);
}
public void downloadByDownloadManager(String url, String fileName1) {
downloadUrl=url;
fileName=fileName1;
request = new DownloadManager.Request(Uri.parse(url));
request.setDescription("video file");
request.setTitle(fileName);
request.setNotificationVisibility(2);
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
request.setDestinationInExternalPublicDir(Constants.PROFILE_VIDEO_FOLDER, fileName);
DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);
// get download service and enqueue file
}
Please help that how to add missing video file later to playlist if it is not downloaded.
To add new video files to your playlist, you need a new MediaSource implementation which can handle a list of sources to enable resizing. This is fairly simple to achieve, the easiest way to do so is to create a modified implementation of ConcaternatingMediaSource which uses lists instead of arrays to store and iterate over media sources. You then replace the ConcaternatingMediaSource in playAndUpdateVideo with the new implementation using lists. This will allow you to add and remove from your playlist as you wish, you can append new media files when your download complete listener is triggered. Here is the full class for a media source implementation using lists:
public final class DynamicMediaSource implements MediaSource {
private List<MediaSource> mediaSources;
private SparseArray<Timeline> timelines;
private SparseArray<Object> manifests;
private Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private SparseArray<Boolean> duplicateFlags;
private boolean isRepeatOneAtomic;
private Listener listener;
private DynamicTimeline timeline;
/**
* #param mediaSources The {#link MediaSource}s to concatenate. It is valid for the same
* {#link MediaSource} instance to be present more than once in the array.
*/
public DynamicMediaSource(List<MediaSource> mediaSources) {
this(false, mediaSources);
}
/**
* #param isRepeatOneAtomic Whether the concatenated media source shall be treated as atomic
* (i.e., repeated in its entirety) when repeat mode is set to {#code Player.REPEAT_MODE_ONE}.
* #param mediaSources The {#link MediaSource}s to concatenate. It is valid for the same
* {#link MediaSource} instance to be present more than once in the array.
*/
public DynamicMediaSource(boolean isRepeatOneAtomic, List<MediaSource> mediaSources) {
for (MediaSource mediaSource : mediaSources) {
Assertions.checkNotNull(mediaSource);
}
this.mediaSources = mediaSources;
this.isRepeatOneAtomic = isRepeatOneAtomic;
timelines = new SparseArray<Timeline>();
manifests = new SparseArray<Object>();
sourceIndexByMediaPeriod = new HashMap<>();
duplicateFlags = buildDuplicateFlags(mediaSources);
}
#Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
this.listener = listener;
for (int i = 0; i < mediaSources.size(); i++) {
if (!duplicateFlags.get(i)) {
final int index = i;
mediaSources.get(i).prepareSource(player, false, new Listener() {
#Override
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
handleSourceInfoRefreshed(index, timeline, manifest);
}
});
}
}
}
#Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
for (int i = 0; i < mediaSources.size(); i++) {
if (!duplicateFlags.get(i)) {
mediaSources.get(i).maybeThrowSourceInfoRefreshError();
}
}
}
#Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
int sourceIndex = timeline.getChildIndexByPeriodIndex(id.periodIndex);
MediaPeriodId periodIdInSource =
new MediaPeriodId(id.periodIndex - timeline.getFirstPeriodIndexByChildIndex(sourceIndex));
MediaPeriod mediaPeriod = mediaSources.get(sourceIndex).createPeriod(periodIdInSource, allocator);
sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex);
return mediaPeriod;
}
#Override
public void releasePeriod(MediaPeriod mediaPeriod) {
int sourceIndex = sourceIndexByMediaPeriod.get(mediaPeriod);
sourceIndexByMediaPeriod.remove(mediaPeriod);
mediaSources.get(sourceIndex).releasePeriod(mediaPeriod);
}
#Override
public void releaseSource() {
for (int i = 0; i < mediaSources.size(); i++) {
if (!duplicateFlags.get(i)) {
mediaSources.get(i).releaseSource();
}
}
}
private void handleSourceInfoRefreshed(int sourceFirstIndex, Timeline sourceTimeline,
Object sourceManifest) {
// Set the timeline and manifest.
timelines.put(sourceFirstIndex, sourceTimeline);
manifests.put(sourceFirstIndex, sourceManifest);
// Also set the timeline and manifest for any duplicate entries of the same source.
for (int i = sourceFirstIndex + 1; i < mediaSources.size(); i++) {
if (mediaSources.get(i).equals(mediaSources.get(sourceFirstIndex))) {
timelines.put(i, sourceTimeline);
manifests.put(i, sourceManifest);
}
}
for(int i= 0; i<mediaSources.size(); i++){
if(timelines.get(i) == null){
// Don't invoke the listener until all sources have timelines.
return;
}
}
timeline = new DynamicTimeline(timelines, isRepeatOneAtomic);
listener.onSourceInfoRefreshed(timeline, new ArrayList(asList(manifests)));
}
private static SparseArray<Boolean> buildDuplicateFlags(List<MediaSource> mediaSources) {
SparseArray<Boolean> duplicateFlags = new SparseArray<Boolean>();
IdentityHashMap<MediaSource, Void> sources = new IdentityHashMap<>(mediaSources.size());
for (int i = 0; i < mediaSources.size(); i++) {
MediaSource source = mediaSources.get(i);
if (!sources.containsKey(source)) {
sources.put(source, null);
duplicateFlags.append(i,false);
} else {
duplicateFlags.append(i,true);
}
}
return duplicateFlags;
}
/**
* A {#link Timeline} that is the concatenation of one or more {#link Timeline}s.
*/
public static final class DynamicTimeline extends AbstractConcatenatedTimeline {
private final SparseArray<Timeline> timelines;
private final int[] sourcePeriodOffsets;
private final int[] sourceWindowOffsets;
private final boolean isRepeatOneAtomic;
public DynamicTimeline(SparseArray<Timeline> timelines, boolean isRepeatOneAtomic) {
super(timelines.size());
int[] sourcePeriodOffsets = new int[timelines.size()];
int[] sourceWindowOffsets = new int[timelines.size()];
long periodCount = 0;
int windowCount = 0;
for (int i = 0; i < timelines.size(); i++) {
Timeline timeline = timelines.get(i);
periodCount += timeline.getPeriodCount();
Assertions.checkState(periodCount <= Integer.MAX_VALUE,
"ConcatenatingMediaSource children contain too many periods");
sourcePeriodOffsets[i] = (int) periodCount;
windowCount += timeline.getWindowCount();
sourceWindowOffsets[i] = windowCount;
}
this.timelines = timelines;
this.sourcePeriodOffsets = sourcePeriodOffsets;
this.sourceWindowOffsets = sourceWindowOffsets;
this.isRepeatOneAtomic = isRepeatOneAtomic;
}
#Override
public int getWindowCount() {
return sourceWindowOffsets[sourceWindowOffsets.length - 1];
}
#Override
public int getPeriodCount() {
return sourcePeriodOffsets[sourcePeriodOffsets.length - 1];
}
#Override
public int getNextWindowIndex(int windowIndex, #Player.RepeatMode int repeatMode) {
if (isRepeatOneAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
repeatMode = Player.REPEAT_MODE_ALL;
}
return super.getNextWindowIndex(windowIndex, repeatMode);
}
#Override
public int getPreviousWindowIndex(int windowIndex, #Player.RepeatMode int repeatMode) {
if (isRepeatOneAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
repeatMode = Player.REPEAT_MODE_ALL;
}
return super.getPreviousWindowIndex(windowIndex, repeatMode);
}
#Override
public int getChildIndexByPeriodIndex(int periodIndex) {
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1;
}
#Override
protected int getChildIndexByWindowIndex(int windowIndex) {
return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1;
}
#Override
protected int getChildIndexByChildUid(Object childUid) {
if (!(childUid instanceof Integer)) {
return C.INDEX_UNSET;
}
return (Integer) childUid;
}
#Override
protected Timeline getTimelineByChildIndex(int childIndex) {
return timelines.get(childIndex);
}
#Override
public int getFirstPeriodIndexByChildIndex(int childIndex) {
return childIndex == 0 ? 0 : sourcePeriodOffsets[childIndex - 1];
}
#Override
protected int getFirstWindowIndexByChildIndex(int childIndex) {
return childIndex == 0 ? 0 : sourceWindowOffsets[childIndex - 1];
}
#Override
protected Object getChildUidByChildIndex(int childIndex) {
return childIndex;
}
}
}
To check when the file is downloaded and set a download completed listener, you can use a BroadcastReceiver. A detailed example of how to set up the BroadcastReceiver is provided here.

ExoPlayer playlist throws IndexOutOfBoundsException with LoopingMediaSource

I have to play video A once, and after it finished, loop video B indefinitely. I'm trying to use ConcatenatingMediaSource for that:
private SimpleExoPlayer initPlayer(ViewGroup layout, int playerViewId, ExoPlayer.EventListener eventListener) {
// 1. Create a default TrackSelector
TrackSelector trackSelector = new DefaultTrackSelector();
// 2. Create a default LoadControl
LoadControl loadControl = new DefaultLoadControl();
// 3. Create the player
this.player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, loadControl);
SimpleExoPlayerView simpleExoPlayerView = (SimpleExoPlayerView) layout.findViewById(playerViewId);
// Bind the player to the view.
simpleExoPlayerView.setUseController(false);
simpleExoPlayerView.setPlayer(player);
if (eventListener != null)
player.addListener(eventListener);
// Prepare the player with the source.
player.setPlayWhenReady(true);
return player;
}
public void startPlayer(String firstURL, String loopingURL) {
initProxy();
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
MediaSource firstSource = getVideoPlayerMediaSource(bandwidthMeter, firstURL);
MediaSource secondSource = new LoopingMediaSource(getVideoPlayerMediaSource(bandwidthMeter, loopingURL));
ConcatenatingMediaSource concatenatedSource =
new ConcatenatingMediaSource(firstSource, secondSource);
player.setPlayWhenReady(true);
player.prepare(concatenatedSource);
player.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
setPlayerPlaying(true);
}
private void initProxy() {
if (proxy == null)
proxy = VideoCache.getProxy(getContext());
}
#NonNull
private MediaSource getVideoPlayerMediaSource(DefaultBandwidthMeter bandwidthMeter, String videoUrl) {
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getContext(),
Util.getUserAgent(getContext(), "com.myapp"), bandwidthMeter);
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
Uri url = Uri.parse(videoUrl);
MediaSource videoSource;
if (videoUrl.contains(".mp4")) {
url = Uri.parse(proxy.getProxyUrl(videoUrl));
videoSource = new ExtractorMediaSource(url,
dataSourceFactory, extractorsFactory, null, null);
} else {
videoSource = new HlsMediaSource(url, dataSourceFactory, null, null);
}
return videoSource;
}
But this throws:
Internal runtime error.
java.lang.IndexOutOfBoundsException
at com.google.android.exoplayer2.util.Assertions.checkIndex(Assertions.java:66)
at com.google.android.exoplayer2.ExoPlayerImplInternal.getPeriodPosition(ExoPlayerImplInternal.java:1077)
at com.google.android.exoplayer2.ExoPlayerImplInternal.getPeriodPosition(ExoPlayerImplInternal.java:1059)
at com.google.android.exoplayer2.ExoPlayerImplInternal.getPeriodPosition(ExoPlayerImplInternal.java:1050)
at com.google.android.exoplayer2.ExoPlayerImplInternal.handleSourceInfoRefreshed(ExoPlayerImplInternal.java:872)
at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:320)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:148)
at android.os.HandlerThread.run(HandlerThread.java:61)
at com.google.android.exoplayer2.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
The problem happens only when using the secondSource as LoopingMediaSource. It works without it, but obviously doesn't loop the second video.
(ExoPlayer version r2.3.1)
Try using the other constructor for your LoopingMediaSource, where you specify a loop count and see if that works:
public LoopingMediaSource(MediaSource childSource, int loopCount) {
Assertions.checkArgument(loopCount > 0);
this.childSource = childSource;
this.loopCount = loopCount;
}
It looks like you are getting a handleSourceInfoRefreshed message, which is sent from the LoopingMediaSource prepareSource() method:
#Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, final Listener listener) {
childSource.prepareSource(player, false, new Listener() {
#Override
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
childPeriodCount = timeline.getPeriodCount();
listener.onSourceInfoRefreshed(new LoopingTimeline(timeline, loopCount), manifest);
}
});
}
But that creates a new LoopingTimeline object and passes it loopCount, which is 0 in your case. The LoopingTimeline class overrides getWindowCount():
#Override
public int getWindowCount() {
return childWindowCount * loopCount;
}
Which will return 0 if the loopCount is 0.
If that works, then you can get your video to loop indefinitely by passing (Integer.MAX_VALUE - 1) as the loopCount, since the LoopingTimeline will cap the loop count anyway.

Categories

Resources