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.
Related
I want to be able to play all audio from one app on only one channel on a device e.g only on left speakers.
I can do this with MediaPlayer playing an mp3 file like this to play only on left speaker
MediaPlayer mediaPlayer=MediaPlayer.create(this,R.raw.bitter_sweet);
mediaPlayer.setVolume(1.0f, 0f);
mediaPlayer.start();
But I want ALL audio from the app to play on left speaker, i.e if i add a webview and load a youtube url the audio should still play on one channel.
Using Exoplayer I can achieve this by creating a custom AudioProcessor adopted from the ChannelMappingAudioProcessor
public class StereoVolumeProcessor implements AudioProcessor {
private int channelCount;
private int sampleRateHz;
private int[] pendingOutputChannels;
private boolean active;
private int[] outputChannels;
private ByteBuffer buffer;
private ByteBuffer outputBuffer;
private boolean inputEnded;
private float[] volume;
private static final int LEFT_SPEAKER = 0;
private static final int RIGHT_SPEAKER = 1;
public StereoVolumeProcessor() {
buffer = EMPTY_BUFFER;
outputBuffer = EMPTY_BUFFER;
channelCount = Format.NO_VALUE;
sampleRateHz = Format.NO_VALUE;
}
public void setChannelMap(int[] outputChannels) {
pendingOutputChannels = outputChannels;
}
#Override
public boolean configure(int sampleRateHz, int channelCount, #C.Encoding int encoding)
throws UnhandledFormatException {
if(volume == null){
throw new IllegalStateException("volume has not been set! Call setVolume(float left,float right)");
}
boolean outputChannelsChanged = !Arrays.equals(pendingOutputChannels, outputChannels);
outputChannels = pendingOutputChannels;
if (outputChannels == null) {
active = false;
return outputChannelsChanged;
}
if (encoding != C.ENCODING_PCM_16BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
if (!outputChannelsChanged && this.sampleRateHz == sampleRateHz
&& this.channelCount == channelCount) {
return false;
}
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
active = true;
return true;
}
#Override
public boolean isActive() {
return active;
}
#Override
public int getOutputChannelCount() {
return outputChannels == null ? channelCount : outputChannels.length;
}
#Override
public int getOutputEncoding() {
return C.ENCODING_PCM_16BIT;
}
/**
* Returns the sample rate of audio output by the processor, in hertz. The value may change as a
* result of calling {#link #configure(int, int, int)} and is undefined if the instance is not
* active.
*/
#Override
public int getOutputSampleRateHz() {
return sampleRateHz;
}
#Override
public void queueInput(ByteBuffer inputBuffer) {
int position = inputBuffer.position();
int limit = inputBuffer.limit();
int size = limit - position;
if (buffer.capacity() < size) {
buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
} else {
buffer.clear();
}
if(isActive()){
int ch = 0;
for(int i = position;i<limit;i+=2){
short sample = (short) (inputBuffer.getShort(i)* volume[ch++]);
buffer.putShort(sample);
ch%=channelCount;
}
}else{
throw new IllegalStateException();
}
inputBuffer.position(limit);
buffer.flip();
outputBuffer = buffer;
}
#Override
public void queueEndOfStream() {
inputEnded = true;
}
/**
* Sets the volume of right and left channels/speakers
* The values are between 0.0 and 1.0
*
* #param left
* #param right
*/
public void setVolume(float left,float right){
volume = new float[]{left,right};
}
public float getLeftVolume(){
return volume[LEFT_SPEAKER];
}
public float getRightVolume(){
return volume[RIGHT_SPEAKER];
}
#Override
public ByteBuffer getOutput() {
ByteBuffer outputBuffer = this.outputBuffer;
this.outputBuffer = EMPTY_BUFFER;
return outputBuffer;
}
#SuppressWarnings("ReferenceEquality")
#Override
public boolean isEnded() {
return inputEnded && outputBuffer == EMPTY_BUFFER;
}
#Override
public void flush() {
outputBuffer = EMPTY_BUFFER;
inputEnded = false;
}
#Override
public void reset() {
flush();
buffer = EMPTY_BUFFER;
channelCount = Format.NO_VALUE;
sampleRateHz = Format.NO_VALUE;
outputChannels = null;
active = false;
}
}
Volume can be set with a value between 0.0 and 1.0 on either left, right or both speakers.
Use the processor as follows
stereoVolumeProcessor = new StereoVolumeProcessor();
stereoVolumeProcessor.setChannelMap(new int[]{0,1});
stereoVolumeProcessor.setVolume(1,0);
RenderersFactory factory = new DefaultRenderersFactory(this){
/**
* Builds an array of {#link AudioProcessor}s that will process PCM audio before output.
*/
#Override
protected AudioProcessor[] buildAudioProcessors() {
return new AudioProcessor[] {stereoVolumeProcessor};
}
};
I have ExoPlayer which plays HLS videos, the thing is i need to give user ability to change video quality(auto/1080/720/480).
I figured out that playing around with AdaptiveTrackSelection.Factory does set the quality, but it remains till the object is killed.
I have also tried using MappingTrackSelector, i know that my video has 4 tracks, but i did not get how to select any of it manually. Will this selection make it work?
Thanks for any ideas.
MappingTrackSelector.MappedTrackInfo trackInfo = mDefaultTrackSelector.getCurrentMappedTrackInfo();
mDefaultTrackSelector.selectTracks(
//what should go here?
, trackInfo.getTrackGroups(4));
Regarding this thread :https://github.com/google/ExoPlayer/issues/2250, I managed to change exo player video quality while playing previous one, so it does not getting in buffering instantly.
So I have next classes :
public enum HLSQuality {
Auto, Quality1080, Quality720, Quality480, NoValue
}
class HLSUtil {
private HLSUtil() {
}
#NonNull
static HLSQuality getQuality(#NonNull Format format) {
switch (format.height) {
case 1080: {
return HLSQuality.Quality1080;
}
case 720: {
return HLSQuality.Quality720;
}
case 480:
case 486: {
return HLSQuality.Quality480;
}
default: {
return HLSQuality.NoValue;
}
}
}
static boolean isQualityPlayable(#NonNull Format format) {
return format.height <= 1080;
}
}
public class ClassAdaptiveTrackSelection extends BaseTrackSelection {
public static final class Factory implements TrackSelection.Factory {
private final BandwidthMeter bandwidthMeter;
private final int maxInitialBitrate = 2000000;
private final int minDurationForQualityIncreaseMs = 10000;
private final int maxDurationForQualityDecreaseMs = 25000;
private final int minDurationToRetainAfterDiscardMs = 25000;
private final float bandwidthFraction = 0.75f;
private final float bufferedFractionToLiveEdgeForQualityIncrease = 0.75f;
public Factory(BandwidthMeter bandwidthMeter) {
this.bandwidthMeter = bandwidthMeter;
}
#Override
public ClassAdaptiveTrackSelection createTrackSelection(TrackGroup group, int... tracks) {
Log.d(ClassAdaptiveTrackSelection.class.getSimpleName(), " Video player quality reset to Auto");
sHLSQuality = HLSQuality.Auto;
return new ClassAdaptiveTrackSelection(
group,
tracks,
bandwidthMeter,
maxInitialBitrate,
minDurationForQualityIncreaseMs,
maxDurationForQualityDecreaseMs,
minDurationToRetainAfterDiscardMs,
bandwidthFraction,
bufferedFractionToLiveEdgeForQualityIncrease
);
}
}
private static HLSQuality sHLSQuality = HLSQuality.Auto;
private final BandwidthMeter bandwidthMeter;
private final int maxInitialBitrate;
private final long minDurationForQualityIncreaseUs;
private final long maxDurationForQualityDecreaseUs;
private final long minDurationToRetainAfterDiscardUs;
private final float bandwidthFraction;
private final float bufferedFractionToLiveEdgeForQualityIncrease;
private int selectedIndex;
private int reason;
private ClassAdaptiveTrackSelection(TrackGroup group,
int[] tracks,
BandwidthMeter bandwidthMeter,
int maxInitialBitrate,
long minDurationForQualityIncreaseMs,
long maxDurationForQualityDecreaseMs,
long minDurationToRetainAfterDiscardMs,
float bandwidthFraction,
float bufferedFractionToLiveEdgeForQualityIncrease) {
super(group, tracks);
this.bandwidthMeter = bandwidthMeter;
this.maxInitialBitrate = maxInitialBitrate;
this.minDurationForQualityIncreaseUs = minDurationForQualityIncreaseMs * 1000L;
this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L;
this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L;
this.bandwidthFraction = bandwidthFraction;
this.bufferedFractionToLiveEdgeForQualityIncrease = bufferedFractionToLiveEdgeForQualityIncrease;
selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE);
reason = C.SELECTION_REASON_INITIAL;
}
#Override
public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs, long availableDurationUs) {
long nowMs = SystemClock.elapsedRealtime();
// Stash the current selection, then make a new one.
int currentSelectedIndex = selectedIndex;
selectedIndex = determineIdealSelectedIndex(nowMs);
if (selectedIndex == currentSelectedIndex) {
return;
}
if (!isBlacklisted(currentSelectedIndex, nowMs)) {
// Revert back to the current selection if conditions are not suitable for switching.
Format currentFormat = getFormat(currentSelectedIndex);
Format selectedFormat = getFormat(selectedIndex);
if (selectedFormat.bitrate > currentFormat.bitrate
&& bufferedDurationUs < minDurationForQualityIncreaseUs(availableDurationUs)) {
// The selected track is a higher quality, but we have insufficient buffer to safely switch
// up. Defer switching up for now.
selectedIndex = currentSelectedIndex;
} else if (selectedFormat.bitrate < currentFormat.bitrate
&& bufferedDurationUs >= maxDurationForQualityDecreaseUs) {
// The selected track is a lower quality, but we have sufficient buffer to defer switching
// down for now.
selectedIndex = currentSelectedIndex;
}
}
// If we adapted, update the trigger.
if (selectedIndex != currentSelectedIndex) {
reason = C.SELECTION_REASON_ADAPTIVE;
}
}
#Override
public int getSelectedIndex() {
return selectedIndex;
}
#Override
public int getSelectionReason() {
return reason;
}
#Override
public Object getSelectionData() {
return null;
}
#Override
public int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
if (queue.isEmpty()) {
return 0;
}
int queueSize = queue.size();
long bufferedDurationUs = queue.get(queueSize - 1).endTimeUs - playbackPositionUs;
if (bufferedDurationUs < minDurationToRetainAfterDiscardUs) {
return queueSize;
}
int idealSelectedIndex = determineIdealSelectedIndex(SystemClock.elapsedRealtime());
Format idealFormat = getFormat(idealSelectedIndex);
// If the chunks contain video, discard from the first SD chunk beyond
// minDurationToRetainAfterDiscardUs whose resolution and bitrate are both lower than the ideal
// track.
for (int i = 0; i < queueSize; i++) {
MediaChunk chunk = queue.get(i);
Format format = chunk.trackFormat;
long durationBeforeThisChunkUs = chunk.startTimeUs - playbackPositionUs;
if (durationBeforeThisChunkUs >= minDurationToRetainAfterDiscardUs
&& format.bitrate < idealFormat.bitrate
&& format.height != Format.NO_VALUE && format.height < 720
&& format.width != Format.NO_VALUE && format.width < 1280
&& format.height < idealFormat.height) {
return i;
}
}
return queueSize;
}
private int determineIdealSelectedIndex(long nowMs) {
if (sHLSQuality != HLSQuality.Auto) {
Log.d(ClassAdaptiveTrackSelection.class.getSimpleName(), " Video player quality seeking for " + String.valueOf(sHLSQuality));
for (int i = 0; i < length; i++) {
Format format = getFormat(i);
if (HLSUtil.getQuality(format) == sHLSQuality) {
Log.d(ClassAdaptiveTrackSelection.class.getSimpleName(), " Video player quality set to " + String.valueOf(sHLSQuality));
return i;
}
}
}
Log.d(ClassAdaptiveTrackSelection.class.getSimpleName(), " Video player quality seeking for auto quality " + String.valueOf(sHLSQuality));
long bitrateEstimate = bandwidthMeter.getBitrateEstimate();
long effectiveBitrate = bitrateEstimate == BandwidthMeter.NO_ESTIMATE
? maxInitialBitrate : (long) (bitrateEstimate * bandwidthFraction);
int lowestBitrateNonBlacklistedIndex = 0;
for (int i = 0; i < length; i++) {
if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) {
Format format = getFormat(i);
if (format.bitrate <= effectiveBitrate && HLSUtil.isQualityPlayable(format)) {
Log.d(ClassAdaptiveTrackSelection.class.getSimpleName(), " Video player quality auto quality found " + String.valueOf(sHLSQuality));
return i;
} else {
lowestBitrateNonBlacklistedIndex = i;
}
}
}
return lowestBitrateNonBlacklistedIndex;
}
private long minDurationForQualityIncreaseUs(long availableDurationUs) {
boolean isAvailableDurationTooShort = availableDurationUs != C.TIME_UNSET
&& availableDurationUs <= minDurationForQualityIncreaseUs;
return isAvailableDurationTooShort
? (long) (availableDurationUs * bufferedFractionToLiveEdgeForQualityIncrease)
: minDurationForQualityIncreaseUs;
}
static void setHLSQuality(HLSQuality HLSQuality) {
sHLSQuality = HLSQuality;
}
}
Hope it helps someone.
You can check out ExoPlayer_TrackSelection from github for changing video quality manually.
I'd like to use ExoPlayer2 with playlists having possibility to dinamically change the tracks (add or remove them from playlist) and change the loop settings.
Since ConcatenatingMediaSource has static arrays (and not lists), I'm implementing a DynamicMediaSource, like Concatenating one but with lists instead of arrays and one mode method addSource to add one more media source to the list.
public void addSource(MediaSource mediaSource) {
this.mediaSources.add(mediaSource);
duplicateFlags = buildDuplicateFlags(this.mediaSources);
if(!mediaSources.isEmpty())
prepareSource(mediaSources.size() -1);
else
prepareSource(0);
}
When I invoke addSource
MediaSource ms = buildMediaSource(mynewuri, null);
mediaSource.addSource(ms);
the track is added to the arrays but it seems something is missing because I always obtain ArrayOutOfBoundsException in createPeriod method.
In createPeriod the method
mediaSources.get(sourceIndex)...
is trying to access the index = mediaSources.size().
Can you help me?
I eventually managed it.
It was my fault during the conversion from arrays to lists.
I had to use SparseArrays for timelines and manifests and everything began to work.
In the DynamicMediaSource simply set the following types:
private final List<MediaSource> mediaSources;
private final SparseArray<Timeline> timelines;
private final SparseArray<Object> manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private SparseArray<Boolean> duplicateFlags;
you have to use sparse arrays to set the proper values into the timelines and manifests in the method
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(new ArrayList(asList(timelines)));
listener.onSourceInfoRefreshed(timeline, new ArrayList(asList(manifests)));
}
Here is the complete code of DynamicMediaSource class:
public final class DynamicMediaSource implements MediaSource {
private static final String TAG = "DynamicSource";
private final List<MediaSource> mediaSources;
private final List<Timeline> timelines;
private final List<Object> manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private SparseArray<Boolean> duplicateFlags;
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(MediaSource... mediaSources) {
this.mediaSources = new ArrayList<MediaSource>(Arrays.asList(mediaSources));
timelines = new ArrayList<Timeline>();
manifests = new ArrayList<Object>();
sourceIndexByMediaPeriod = new HashMap<>();
duplicateFlags = buildDuplicateFlags(this.mediaSources);
}
public void addSource(MediaSource mediaSource) {
this.mediaSources.add(mediaSource);
duplicateFlags = buildDuplicateFlags(this.mediaSources);
/*if(!mediaSources.isEmpty())
prepareSource(mediaSources.size() -1);
else
prepareSource(0);*/
}
#Override
public void prepareSource(Listener listener) {
this.listener = listener;
for (int i = 0; i < mediaSources.size(); i++) {
prepareSource(i);
/*if (duplicateFlags.get(i) == null || !duplicateFlags.get(i)) {
final int index = i;
mediaSources.get(i).prepareSource(new Listener() {
#Override
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
handleSourceInfoRefreshed(index, timeline, manifest);
}
});
}*/
}
}
private void prepareSource(int sourceindex) {
if (duplicateFlags.get(sourceindex) == null || !duplicateFlags.get(sourceindex)) {
final int index = sourceindex;
mediaSources.get(sourceindex).prepareSource(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) == null || !duplicateFlags.get(i)) {
mediaSources.get(i).maybeThrowSourceInfoRefreshError();
}
}
}
#Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
long positionUs) {
int sourceIndex = timeline.getSourceIndexForPeriod(index);
int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex);
MediaPeriod mediaPeriod = mediaSources.get(sourceIndex).createPeriod(periodIndexInSource, callback,
allocator, positionUs);
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) == null || !duplicateFlags.get(i)) {
mediaSources.get(i).releaseSource();
}
}
}
private void handleSourceInfoRefreshed(int sourceFirstIndex, Timeline sourceTimeline,
Object sourceManifest) {
// Set the timeline and manifest.
timelines.add(sourceFirstIndex, sourceTimeline);
manifests.add(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.add(i, sourceTimeline);
manifests.add(i, sourceManifest);
}
}
for (Timeline timeline : timelines) {
if (timeline == null) {
// Don't invoke the listener until all sources have timelines.
return;
}
}
timeline = new DynamicTimeline(new ArrayList(timelines));
listener.onSourceInfoRefreshed(timeline, new ArrayList(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 mediaSource = mediaSources.get(i);
if (!sources.containsKey(mediaSource)) {
sources.put(mediaSource, null);
} else {
duplicateFlags.setValueAt(i, true);
}
}
return duplicateFlags;
}
/**
* A {#link Timeline} that is the concatenation of one or more {#link Timeline}s.
*/
private static final class DynamicTimeline extends Timeline {
private final List<Timeline> timelines;
private final List<Integer> sourcePeriodOffsets;
private final List<Integer> sourceWindowOffsets;
public DynamicTimeline(List<Timeline> timelines) {
List<Integer> sourcePeriodOffsets = new ArrayList<>();
List<Integer> sourceWindowOffsets = new ArrayList<>();
int periodCount = 0;
int windowCount = 0;
for (Timeline timeline : timelines) {
periodCount += timeline.getPeriodCount();
windowCount += timeline.getWindowCount();
sourcePeriodOffsets.add(periodCount);
sourceWindowOffsets.add(windowCount);
}
this.timelines = timelines;
this.sourcePeriodOffsets = sourcePeriodOffsets;
this.sourceWindowOffsets = sourceWindowOffsets;
}
#Override
public int getWindowCount() {
return sourceWindowOffsets.get(sourceWindowOffsets.size() - 1);
}
#Override
public Window getWindow(int windowIndex, Window window, boolean setIds) {
int sourceIndex = getSourceIndexForWindow(windowIndex);
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
timelines.get(sourceIndex).getWindow(windowIndex - firstWindowIndexInSource, window, setIds);
window.firstPeriodIndex += firstPeriodIndexInSource;
window.lastPeriodIndex += firstPeriodIndexInSource;
return window;
}
#Override
public int getPeriodCount() {
return sourcePeriodOffsets.get(sourcePeriodOffsets.size() - 1);
}
#Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
int sourceIndex = getSourceIndexForPeriod(periodIndex);
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
timelines.get(sourceIndex).getPeriod(periodIndex - firstPeriodIndexInSource, period, setIds);
period.windowIndex += firstWindowIndexInSource;
if (setIds) {
period.uid = Pair.create(sourceIndex, period.uid);
}
return period;
}
#Override
public int getIndexOfPeriod(Object uid) {
if (!(uid instanceof Pair)) {
return C.INDEX_UNSET;
}
Pair<?, ?> sourceIndexAndPeriodId = (Pair<?, ?>) uid;
if (!(sourceIndexAndPeriodId.first instanceof Integer)) {
return C.INDEX_UNSET;
}
int sourceIndex = (Integer) sourceIndexAndPeriodId.first;
Object periodId = sourceIndexAndPeriodId.second;
if (sourceIndex < 0 || sourceIndex >= timelines.size()) {
return C.INDEX_UNSET;
}
int periodIndexInSource = timelines.get(sourceIndex).getIndexOfPeriod(periodId);
return periodIndexInSource == C.INDEX_UNSET ? C.INDEX_UNSET
: getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource;
}
private int getSourceIndexForPeriod(int periodIndex) {
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1;
}
private int getFirstPeriodIndexInSource(int sourceIndex) {
return sourceIndex == 0 ? 0 : sourcePeriodOffsets.get(sourceIndex - 1);
}
private int getSourceIndexForWindow(int windowIndex) {
return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1;
}
private int getFirstWindowIndexInSource(int sourceIndex) {
return sourceIndex == 0 ? 0 : sourceWindowOffsets.get(sourceIndex - 1);
}
}
}
please help guys,, here is my code for my simple gallery android app for external images
I made the loader is in array , and I made swiping action ,,
what I think I have to chose Wither removechild nor enter frame but I can't apply it
coz when publish, things is mixed up and swiping is not working well :(
could you please help me to fix it it's AS3 at Flash 5.5
var pictureArray:Array = new Array;
var loader1 = new Loader();
loader1.load(new URLRequest("1.jpg"));
pictureArray.push(loader1);
var loader2 = new Loader();
loader2.load(new URLRequest("2.jpg"));
pictureArray.push(loader2);
var loader3 = new Loader();
loader3.load(new URLRequest("3.jpg"));
pictureArray.push(loader3);
addChild(pictureArray[0]);
pictureArray[0].x = 0; pictureArray[0].y = 0;
var n:int = 0;
Multitouch.inputMode = MultitouchInputMode.GESTURE;
var currentGalleryItem:Number = 1;
var totalGalleryItems:Number = 3;
stage.addEventListener (TransformGestureEvent.GESTURE_SWIPE, fl_SwipeToGoToNextPreviousFrame);
function fl_SwipeToGoToNextPreviousFrame(event:TransformGestureEvent):void
{
if(event.offsetX == 1)
{
if(currentGalleryItem > 1){
currentGalleryItem--;
slideRight();
removeChild(pictureArray[n]);
n = n+1;
if (n>pictureArray.length - 1)
n=0;
addChild(pictureArray[n]);
pictureArray[n].x = 0; pictureArray[n].y = 0;
}
}
else if(event.offsetX == -1)
{
if(currentGalleryItem < totalGalleryItems){
currentGalleryItem++;
slideLeft();
removeChild(pictureArray[n]);
n = n-1;
if (n<0)
n=pictureArray.length - 1;
addChild(pictureArray[n]);
pictureArray[n].x = 0; pictureArray[n].y = 0;
}
}
}
var slideCounter:Number = 0;
function slideLeft(){
(pictureArray[n]).addEventListener("enterFrame", moveGalleryLeft);
}
function slideRight(){
(pictureArray[n]).addEventListener("enterFrame", moveGalleryRight);
}
function moveGalleryLeft(evt:Event){
(pictureArray[n]).x -= 48;
slideCounter++;
if(slideCounter == 5){
(pictureArray[n]).removeEventListener("enterFrame", moveGalleryLeft);
slideCounter = 0;
}
}
function moveGalleryRight(evt:Event){
(pictureArray[n]).x += 48;
slideCounter++;
if(slideCounter == 5){
(pictureArray[n]).removeEventListener("enterFrame", moveGalleryRight);
slideCounter = 0;
}
}
I would recommend load images on demand, for animation use some tweening engine(in my example It's TweenLite), and dispose previous frames, It's mobile device you should honour memory. Most of other comments in the code, I don't have ability to test in on the device, also It's not complete code, It's only carcass ;)
package {
import com.greensock.TweenLite;
import flash.display.Bitmap;
import flash.display.Loader;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.TransformGestureEvent;
import flash.net.URLRequest;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
public class StackOverflow extends Sprite {
private var _loader:Loader;
private var _images:Array;
private var _position:int;
private var _currentFrame:Sprite;
public function StackOverflow() {
addEventListener(Event.ADDED_TO_STAGE, onAdded);
}
private function setup():void {
_loader = new Loader();
_images = ["1.jpg", "2.jpg", "3.jpg"];
_currentFrame = new Sprite();
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadImage);
stage.addEventListener(TransformGestureEvent.GESTURE_SWIPE, onSwipe);
addChild(_currentFrame);
loadImageAtPosition(_position);
}
private function disposeFrame(frame:Sprite):void {
//You can cache loaded bitmaps, or dispose them
removeChild(frame);
var bitmap:Bitmap = frame.getChildAt(0) as Bitmap;
bitmap.bitmapData.dispose();
}
private function loadImageAtPosition(index:int):void {
//By using one loader for all tasks, you will have only last requested image
_loader.load(new URLRequest(_images[index]));
}
private function offsetScreenOn(direction:Number):void {
var offset:int;
var start:int;
if (direction > 0) {
//Direction to the right side
offset = stage.stageWidth;
} else {
//Direction to the left side
offset = -stage.stageWidth;
}
//Starting position for new frame
start = -offset;
//TweenLite is great for such task
if (_currentFrame != null) {
//Remove previous frame
TweenLite.to(_currentFrame, 0.8, {x: offset, onComplete: disposeFrame, onCompleteParams: [_currentFrame]});
}
//Initialise new frame, fill them, decor them...
_currentFrame = new Sprite();
if (_currentFrame != null) {
addChild(_currentFrame);
_currentFrame.x = start;
//Animate in new frame
TweenLite.to(_currentFrame, 0.8, {x: (start + offset)});
}
}
private function onLoadImage(e:Event):void {
if (_currentFrame != null) {
var image:Bitmap = _loader.content as Bitmap;
if (image != null) {
image.smoothing = true;
_currentFrame.addChild(image);
//Resize, displace image for your needs
//Add appear animation if you want
}
}
}
//If you want, you can postpone swipe, while in state of transition
private function onSwipe(e:TransformGestureEvent):void {
//Left to Right swipe
if (e.offsetX == 1) {
if (--_position < 0) {
_position = _images.length;
}
offsetScreenOn(e.offsetX);
loadImageAtPosition(_position);
}
//Right to Left swipe
if (e.offsetX == -1) {
if (++_position >= _images.length) {
_position = 0;
}
offsetScreenOn(e.offsetX);
loadImageAtPosition(_position);
}
}
private function onAdded(e:Event):void {
removeEventListener(Event.ADDED_TO_STAGE, onAdded);
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
Multitouch.inputMode = MultitouchInputMode.GESTURE;
setup();
}
}
}
check this out
simple android gallery
it a basic implementation of a gallery app, it makes use of the android media provider to actually get images in the device storage and provides an image slider by using a viewPager and a RecyclerView as indicator, I will outline the important parts here, you can use a RecyclerView and a Recycler Adapter to display your images
public class picture_Adapter extends RecyclerView.Adapter<PicHolder> {
private ArrayList<pictureFacer> pictureList;
private Context pictureContx;
private final itemClickListener picListerner;
/**
*
* #param pictureList ArrayList of pictureFacer objects
* #param pictureContx The Activities Context
* #param picListerner An interface for listening to clicks on the RecyclerView's items
*/
public picture_Adapter(ArrayList<pictureFacer> pictureList, Context pictureContx,itemClickListener picListerner) {
this.pictureList = pictureList;
this.pictureContx = pictureContx;
this.picListerner = picListerner;
}
#NonNull
#Override
public PicHolder onCreateViewHolder(#NonNull ViewGroup container, int position) {
LayoutInflater inflater = LayoutInflater.from(container.getContext());
View cell = inflater.inflate(R.layout.pic_holder_item, container, false);
return new PicHolder(cell);
}
#Override
public void onBindViewHolder(#NonNull final PicHolder holder, final int position) {
final pictureFacer image = pictureList.get(position);
Glide.with(pictureContx)
.load(image.getPicturePath())
.apply(new RequestOptions().centerCrop())
.into(holder.picture);
setTransitionName(holder.picture, String.valueOf(position) + "_image");
holder.picture.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
picListerner.onPicClicked(holder,position, pictureList);
}
});
}
#Override
public int getItemCount() {
return pictureList.size();
}
}
the method below gets all the device images and load the info for each image in a imageFacer object which you can use to populate the recyclerview adapter
see the full post in the link above
public ArrayList<pictureFacer> getAllImagesByFolder(String path){
ArrayList<pictureFacer> images = new ArrayList<>();
Uri allVideosuri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String[] projection = { MediaStore.Images.ImageColumns.DATA ,MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE};
Cursor cursor = ImageDisplay.this.getContentResolver().query( allVideosuri, projection, MediaStore.Images.Media.DATA + " like ? ", new String[] {"%"+path+"%"}, null);
try {
cursor.moveToFirst();
do{
pictureFacer pic = new pictureFacer();
pic.setPicturName(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)));
pic.setPicturePath(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)));
pic.setPictureSize(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)));
images.add(pic);
}while(cursor.moveToNext());
cursor.close();
ArrayList<pictureFacer> reSelection = new ArrayList<>();
for(int i = images.size()-1;i > -1;i--){
reSelection.add(images.get(i));
}
images = reSelection;
} catch (Exception e) {
e.printStackTrace();
}
return images;
}
In Android 4.x my audio looping app has 0.2 to 0.5 second gaps in the sound loops.
I'm using MediaPlayer as my sounds can be quite large (2-3mb each in some cases) and it can run multiple instances at the same time.
I have researched this quite a bit and I see there is a bug for Android 4.x... however, I have tried many work arounds and I can't seem to get any of them working.
Converted all the wavs to OGG using Audacity (quality level 2 to 10, it didn't matter)
Tried setNextMediaPlayer()
Tried to use seekTo(0) on stop and repeat
Tried soundPool which has its own bugs
Here's a sample of the code I'm using:
public class SoundPlayer implements OnCompletionListener {
private MediaPlayer mp = null;
public void initPlayer() {
if(mp == null) {
mp = new MediaPlayer();
}
}
public void prepare(Context context, int resource) {
initPlayer();
try{
mp.reset();
Uri uri = Uri.parse("android.resource://com.myapp.app/"+resource);
mp.setDataSource(context,uri);
mp.prepare();
isPrepared = true;
mp.setOnCompletionListener(this);
} catch(Exception e) {
e.printStackTrace();
}
}
.......... etc (Uses typical MediaPlayer methods such as stop(), start(), setLooping(true)
}
I'm not using anything special, so I'm just wondering if anyone knows of a work around for the looping bug on Android.
Okay old Post, but this solution works. This is kind of hack. It'll help somebody if somebody came to this answer.
I am just using three media players. mp1, mp2, mp3. mp1 gets play, and I set mp2 to its setNextMediaPlayer. when mp1 ends, I set mp3 to mp2's setNextMediaPlayer. when mp2 ends, I set mp1 to mp3's setNextMediaPlayer. Following is the complete code which play pause, stop, set vol. Just create an object BZMediaPlayer, start it by providing uri, or resource id.
import android.content.Context;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.net.Uri;
import android.util.Log;
public class BZMediaPlayer {
private Context context;
private Uri uri;
private int resourceId;
// which file is getting played
public static final int URI_PLAYING = 1;
public static final int RESOURCE_PLAYING = 2;
private int filePlaying;
// states of the media player
public static final int STATE_PLAYING = 1;
public static final int STATE_PAUSED = 2;
public static final int STATE_STOP = 3;
// current state
private int state = STATE_STOP;
// current mediaPlayer which is playing
private int mediaPlayerIndex = -1;
// 3 media players
private MediaPlayer mp[] = new MediaPlayer[3];
// current volume
private float vol;
public BZMediaPlayer(Context context) {
this.context = context;
}
/**
* plays the provided uri
* #param uri
*/
public void play(Uri uri) {
this.uri = uri;
// current playing file
filePlaying = URI_PLAYING;
// stop any playing session
stop();
// initialize and set listener to three mediaplayers
for (int i = 0; i < mp.length; i++) {
mp[i] = MediaPlayer.create(context, uri);
mp[i].setOnCompletionListener(completionListener);
}
// set nextMediaPlayers
mp[0].setNextMediaPlayer(mp[1]);
mp[1].setNextMediaPlayer(mp[2]);
// start the first MediaPlayer
mp[0].start();
// set mediaplayer inex
mediaPlayerIndex = 0;
// set state
state = STATE_PLAYING;
}
/**
* play file from resource
* #param resourceId
*/
public void play(int resourceId) {
this.resourceId = resourceId;
filePlaying = RESOURCE_PLAYING;
stop();
for (int i = 0; i < mp.length; i++) {
mp[i] = MediaPlayer.create(context, resourceId);
mp[i].setOnCompletionListener(completionListener);
}
mp[0].setNextMediaPlayer(mp[1]);
mp[1].setNextMediaPlayer(mp[2]);
mp[0].start();
mediaPlayerIndex = 0;
state = STATE_PLAYING;
}
/**
* play if the mediaplayer is pause
*/
public void play() {
if (state == STATE_PAUSED) {
mp[mediaPlayerIndex].start();
Log.d("BZMediaPlayer", "playing");
state = STATE_PLAYING;
}
}
/**
* pause current playing session
*/
public void pause() {
if (state == STATE_PLAYING) {
mp[mediaPlayerIndex].pause();
Log.d("BZMediaPlayer", "pausing");
state = STATE_PAUSED;
}
}
/**
* get current state
* #return
*/
public int getState() {
return state;
}
/**
* stop every mediaplayer
*/
public void stop() {
for(int i = 0 ; i < mp.length ; i++) {
if (mp[i] != null) {
mp[i].stop();
if(mp[i].isPlaying()) {
mp[i].release();
}
}
}
state = STATE_STOP;
}
/**
* set vol for every mediaplayer
* #param vol
*/
public void setVol(float vol) {
this.vol = vol;
for(int i = 0 ; i < mp.length ; i++) {
if (mp[i] != null && mp[i].isPlaying()) {
mp[i].setVolume(vol, vol);
}
}
}
/**
* internal listener which handles looping thing
*/
private MediaPlayer.OnCompletionListener completionListener = new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer curmp) {
int mpEnds = 0;
int mpPlaying = 0;
int mpNext = 0;
if(curmp == mp[0]) {
mpEnds = 0;
mpPlaying = 1;
mpNext = 2;
}
else if(curmp == mp[1]) {
mpEnds = 1;
mpPlaying = 2;
mpNext = 0; // corrected, else index out of range
}
else if(curmp == mp[2]) {
mpEnds = 2;
mpPlaying = 0; // corrected, else index out of range
mpNext = 1; // corrected, else index out of range
}
// as we have set mp2 mp1's next, so index will be 1
mediaPlayerIndex = mpPlaying;
Log.d("BZMediaPlayer", "Media Player " + mpEnds);
try {
// mp3 is already playing release it
if (mp[mpNext] != null) {
mp[mpNext].release();
}
// if we are playing uri
if (filePlaying == URI_PLAYING) {
mp[mpNext] = MediaPlayer.create(context, uri);
} else {
mp[mpNext] = MediaPlayer.create(context, resourceId);
}
// at listener to mp3
mp[mpNext].setOnCompletionListener(this);
// set vol
mp[mpNext].setVolume(vol, vol);
// set nextMediaPlayer
mp[mpPlaying].setNextMediaPlayer(mp[mpNext]);
// set nextMediaPlayer vol
mp[mpPlaying].setVolume(vol, vol);
} catch (Exception e) {
e.printStackTrace();
}
}
};
}
Hope it will help somebody
Edit: clean code