package tv.emby.embyatv.playback;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.text.InputType;
import android.widget.EditText;
import com.devbrackets.android.exomedia.EMAudioPlayer;
import com.devbrackets.android.exomedia.event.EMMediaProgressEvent;
import com.devbrackets.android.exomedia.listener.EMProgressCallback;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import mediabrowser.apiinteraction.ApiClient;
import mediabrowser.apiinteraction.Response;
import mediabrowser.apiinteraction.android.profiles.AndroidProfile;
import mediabrowser.model.dlna.AudioOptions;
import mediabrowser.model.dlna.DeviceProfile;
import mediabrowser.model.dlna.StreamInfo;
import mediabrowser.model.dto.BaseItemDto;
import mediabrowser.model.playlists.PlaylistCreationRequest;
import mediabrowser.model.playlists.PlaylistCreationResult;
import tv.emby.embyatv.R;
import tv.emby.embyatv.TvApp;
import tv.emby.embyatv.base.CustomMessage;
import tv.emby.embyatv.itemhandling.AudioQueueItem;
import tv.emby.embyatv.itemhandling.BaseRowItem;
import tv.emby.embyatv.itemhandling.ItemRowAdapter;
import tv.emby.embyatv.presentation.CardPresenter;
import tv.emby.embyatv.querying.QueryType;
import tv.emby.embyatv.util.ProfileHelper;
import tv.emby.embyatv.util.RemoteControlReceiver;
import tv.emby.embyatv.util.Utils;
/**
* Created by Eric on 10/22/2015.
*/
public class MediaManager {
private static ItemRowAdapter mCurrentMediaAdapter;
private static int mCurrentMediaPosition = -1;
private static String currentMediaTitle;
private static ItemRowAdapter mCurrentAudioQueue;
private static ItemRowAdapter mManagedAudioQueue;
private static int mCurrentAudioQueuePosition = -1;
private static BaseItemDto mCurrentAudioItem;
private static StreamInfo mCurrentAudioStreamInfo;
private static long mCurrentAudioPosition;
private static LibVLC mLibVLC;
private static org.videolan.libvlc.MediaPlayer mVlcPlayer;
private static VlcEventHandler mVlcHandler = new VlcEventHandler();
private static EMAudioPlayer mExoplayer;
private static AudioManager mAudioManager;
private static boolean audioInitialized;
private static boolean nativeMode = false;
private static boolean videoQueueModified = false;
private static List<AudioEventListener> mAudioEventListeners = new ArrayList<>();
private static long lastProgressReport;
private static long lastProgressEvent;
private static boolean mRepeat;
private static List<BaseItemDto> mCurrentVideoQueue;
public static ItemRowAdapter getCurrentMediaAdapter() {
return mCurrentMediaAdapter;
}
public static boolean hasAudioQueueItems() { return mCurrentAudioQueue != null && mCurrentAudioQueue.size() > 0; }
public static boolean hasVideoQueueItems() { return mCurrentVideoQueue != null && mCurrentVideoQueue.size() > 0; }
public static void setCurrentMediaAdapter(ItemRowAdapter currentMediaAdapter) {
MediaManager.mCurrentMediaAdapter = currentMediaAdapter;
}
public static int getCurrentMediaPosition() {
return mCurrentMediaPosition;
}
public static void setCurrentVideoQueue(List<BaseItemDto> items) { mCurrentVideoQueue = items; }
public static List<BaseItemDto> getCurrentVideoQueue() { return mCurrentVideoQueue; }
public static int getCurrentAudioQueueSize() { return mCurrentAudioQueue != null ? mCurrentAudioQueue.size() : 0; }
public static int getCurrentAudioQueuePosition() { return mCurrentAudioQueuePosition; }
public static long getCurrentAudioPosition() { return mCurrentAudioPosition; }
public static String getCurrentAudioQueueDisplayPosition() { return Integer.toString(mCurrentAudioQueuePosition >=0 ? mCurrentAudioQueuePosition+1 : 1); }
public static String getCurrentAudioQueueDisplaySize() { return mCurrentAudioQueue != null ? Integer.toString(mCurrentAudioQueue.size()) : "0"; }
public static BaseItemDto getCurrentAudioItem() { return mCurrentAudioItem != null ? mCurrentAudioItem : hasAudioQueueItems() ? ((BaseRowItem)mCurrentAudioQueue.get(0)).getBaseItem() : null; }
public static boolean toggleRepeat() { mRepeat = !mRepeat; return mRepeat; }
public static boolean isRepeatMode() { return mRepeat; }
public static ItemRowAdapter getCurrentAudioQueue() { return mCurrentAudioQueue; }
public static ItemRowAdapter getManagedAudioQueue() {
createManagedAudioQueue();
return mManagedAudioQueue;
}
public static void createManagedAudioQueue() {
if (mCurrentAudioQueue != null) {
if (mManagedAudioQueue != null) {
//re-create existing one
mManagedAudioQueue.clear();
for (int i = mCurrentAudioQueuePosition >= 0 ? mCurrentAudioQueuePosition : 0; i < mCurrentAudioQueue.size(); i++) {
mManagedAudioQueue.add(mCurrentAudioQueue.get(i));
}
} else {
List<BaseItemDto> managedItems = new ArrayList<>();
for (int i = mCurrentAudioQueuePosition >= 0 ? mCurrentAudioQueuePosition : 0; i < mCurrentAudioQueue.size(); i++) {
managedItems.add(((BaseRowItem)mCurrentAudioQueue.get(i)).getBaseItem());
}
mManagedAudioQueue = new ItemRowAdapter(managedItems, new CardPresenter(true, Utils.convertDpToPixel(TvApp.getApplication(), 150)), null, QueryType.StaticAudioQueueItems);
mManagedAudioQueue.Retrieve();
}
if (isPlayingAudio()) {
((BaseRowItem)mManagedAudioQueue.get(0)).setIsPlaying(true);
}
}
}
public static void addAudioEventListener(AudioEventListener listener) {
mAudioEventListeners.add(listener);
TvApp.getApplication().getLogger().Debug("Added event listener. Total listeners: "+mAudioEventListeners.size());
}
public static void removeAudioEventListener(AudioEventListener listener) {
mAudioEventListeners.remove(listener);
TvApp.getApplication().getLogger().Debug("Removed event listener. Total listeners: " + mAudioEventListeners.size());
}
public static boolean initAudio() {
if (mAudioManager == null) mAudioManager = (AudioManager) TvApp.getApplication().getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager == null) {
TvApp.getApplication().getLogger().Error("Unable to get audio manager");
Utils.showToast(TvApp.getApplication(), R.string.msg_cannot_play_time);
return false;
}
return createPlayer(600);
}
private static boolean isPaused() {
return nativeMode ? !mExoplayer.isPlaying() : !mVlcPlayer.isPlaying();
}
private static void reportProgress() {
//Don't need to be too aggressive with these calls - just be sure every second
if (System.currentTimeMillis() < lastProgressEvent + 750) return;
lastProgressEvent = System.currentTimeMillis();
mCurrentAudioPosition = nativeMode ? mExoplayer.getCurrentPosition() : mVlcPlayer.getTime();
//fire external listeners if there
for (AudioEventListener listener : mAudioEventListeners) {
listener.onProgress(mCurrentAudioPosition);
}
//Report progress to server every 5 secs
if (System.currentTimeMillis() > lastProgressReport + 5000) {
Utils.ReportProgress(mCurrentAudioItem, mCurrentAudioStreamInfo, mCurrentAudioPosition*10000, isPaused());
lastProgressReport = System.currentTimeMillis();
TvApp.getApplication().setLastUserInteraction(lastProgressReport);
}
}
private static void onComplete() {
Utils.ReportStopped(mCurrentAudioItem, mCurrentAudioStreamInfo, mCurrentAudioPosition);
nextAudioItem();
//fire external listener if there
for (AudioEventListener listener : mAudioEventListeners) {
TvApp.getApplication().getLogger().Info("Firing playback state change listener for item completion. "+ mCurrentAudioItem.getName());
listener.onPlaybackStateChange(PlaybackController.PlaybackState.IDLE, mCurrentAudioItem);
}
}
private static boolean createPlayer(int buffer) {
try {
// Create a new media player based on platform
if (Utils.is60()) {
nativeMode = true;
mExoplayer = new EMAudioPlayer(TvApp.getApplication());
mExoplayer.setProgressCallback(new EMProgressCallback() {
@Override
public boolean onProgressUpdated(EMMediaProgressEvent progressEvent) {
reportProgress();
return false;
}
});
mExoplayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
onComplete();
}
});
} else {
ArrayList<String> options = new ArrayList<>(20);
options.add("--network-caching=" + buffer);
options.add("--no-audio-time-stretch");
options.add("-v");
mLibVLC = new LibVLC(options);
LibVLC.setOnNativeCrashListener(new LibVLC.OnNativeCrashListener() {
@Override
public void onNativeCrash() {
new Exception().printStackTrace();
//todo put our custom log reporter here...
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
});
mVlcPlayer = new org.videolan.libvlc.MediaPlayer(mLibVLC);
mVlcPlayer.setAudioOutput(Utils.downMixAudio() ? "opensles_android" : "android_audiotrack");
mVlcPlayer.setAudioOutputDevice("hdmi");
mVlcHandler.setOnProgressListener(new PlaybackListener() {
@Override
public void onEvent() {
reportProgress();
}
});
mVlcHandler.setOnCompletionListener(new PlaybackListener() {
@Override
public void onEvent() {
onComplete();
}
});
mVlcPlayer.setEventListener(mVlcHandler);
}
} catch (Exception e) {
TvApp.getApplication().getLogger().ErrorException("Error creating VLC player", e);
Utils.showToast(TvApp.getApplication(), TvApp.getApplication().getString(R.string.msg_video_playback_error));
return false;
}
return true;
}
private static AudioManager.OnAudioFocusChangeListener mAudioFocusChanged = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
pauseAudio();
break;
case AudioManager.AUDIOFOCUS_LOSS:
stopAudio();
mAudioManager.unregisterMediaButtonEventReceiver(new ComponentName(TvApp.getApplication().getPackageName(), RemoteControlReceiver.class.getName()));
break;
case AudioManager.AUDIOFOCUS_GAIN:
//resumeAudio();
break;
}
}
};
private static void fireQueueStatusChange() {
for (AudioEventListener listener : mAudioEventListeners) {
TvApp.getApplication().getLogger().Info("Firing queue state change listener. "+ hasAudioQueueItems());
listener.onQueueStatusChanged(hasAudioQueueItems());
}
}
private static void createAudioQueue(List<BaseItemDto> items) {
mCurrentAudioQueue = new ItemRowAdapter(items, new CardPresenter(true, Utils.convertDpToPixel(TvApp.getApplication(), 140)), null, QueryType.StaticAudioQueueItems);
mCurrentAudioQueue.Retrieve();
mManagedAudioQueue = null;
fireQueueStatusChange();
}
private static int TYPE_AUDIO = 0;
private static int TYPE_VIDEO = 1;
public static void saveAudioQueue(Activity activity) {
saveQueue(activity, TYPE_AUDIO);
}
public static void saveVideoQueue(Activity activity) {
saveQueue(activity, TYPE_VIDEO);
}
public static void saveQueue(Activity activity, final int type) {
//Get a name and save as playlist
final EditText name = new EditText(activity);
name.setInputType(InputType.TYPE_TEXT_FLAG_CAP_WORDS);
new AlertDialog.Builder(activity)
.setTitle(R.string.lbl_save_as_playlist)
.setMessage("Enter a name for the new playlist")
.setView(name)
.setPositiveButton(R.string.btn_done, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final String text = name.getText().toString();
PlaylistCreationRequest request = new PlaylistCreationRequest();
request.setUserId(TvApp.getApplication().getCurrentUser().getId());
request.setMediaType(type == TYPE_AUDIO ? "Audio" : "Video");
request.setName(text);
request.setItemIdList(type == TYPE_AUDIO ? getCurrentAudioQueueItemIds() : getCurrentVideoQueueItemIds());
TvApp.getApplication().getApiClient().CreatePlaylist(request, new Response<PlaylistCreationResult>() {
@Override
public void onResponse(PlaylistCreationResult response) {
TvApp.getApplication().showMessage("Playlist Saved", "Queue saved as new playlist: "+text);
TvApp.getApplication().setLastLibraryChange(Calendar.getInstance());
}
@Override
public void onError(Exception exception) {
TvApp.getApplication().getLogger().Debug(exception.toString());
}
});
}
})
.show();
}
private static ArrayList<String> getCurrentAudioQueueItemIds() {
ArrayList<String> result = new ArrayList<>();
if (mCurrentAudioQueue != null) {
for (int i = 0; i < mCurrentAudioQueue.size(); i++) {
AudioQueueItem item = (AudioQueueItem) mCurrentAudioQueue.get(i);
result.add(item.getItemId());
}
}
return result;
}
private static ArrayList<String> getCurrentVideoQueueItemIds() {
ArrayList<String> result = new ArrayList<>();
if (mCurrentVideoQueue != null) {
for (int i = 0; i < mCurrentVideoQueue.size(); i++) {
result.add(mCurrentVideoQueue.get(i).getId());
}
}
return result;
}
public static int queueAudioItem(int pos, BaseItemDto item) {
if (mCurrentAudioQueue == null) createAudioQueue(new ArrayList<BaseItemDto>());
mCurrentAudioQueue.add(new BaseRowItem(pos, item));
TvApp.getApplication().showMessage(TvApp.getApplication().getString(R.string.msg_added_item_to_queue) + (pos + 1), Utils.GetFullName(item), 4000, R.drawable.audioicon);
return pos;
}
public static int queueAudioItem(BaseItemDto item) {
if (mCurrentAudioQueue == null) createAudioQueue(new ArrayList<BaseItemDto>());
mCurrentAudioQueue.add(new AudioQueueItem(mCurrentAudioQueue.size(), item));
return mCurrentAudioQueue.size()-1;
}
public static int addToVideoQueue(BaseItemDto item) {
if (mCurrentVideoQueue == null) mCurrentVideoQueue = new ArrayList<>();
mCurrentVideoQueue.add(item);
videoQueueModified = true;
TvApp.getApplication().setLastVideoQueueChange(System.currentTimeMillis());
if (mCurrentVideoQueue.size() == 1 && TvApp.getApplication().getCurrentActivity() != null) {
TvApp.getApplication().getCurrentActivity().sendMessage(CustomMessage.RefreshRows);
}
long total = System.currentTimeMillis();
for (BaseItemDto video :
mCurrentVideoQueue) {
total += video.getRunTimeTicks() / 10000;
}
Utils.showToast(TvApp.getApplication(), item.getName() + " added to video queue. Ends: "+android.text.format.DateFormat.getTimeFormat(TvApp.getApplication()).format(new Date(total)));
return mCurrentVideoQueue.size()-1;
}
public static void clearAudioQueue() {
if (mCurrentAudioQueue == null) {
createAudioQueue(new ArrayList<BaseItemDto>());
}
else {
mCurrentAudioQueue.clear();
fireQueueStatusChange();
}
mCurrentAudioQueuePosition = -1;
if (mManagedAudioQueue != null) mManagedAudioQueue.clear();
}
public static void addToAudioQueue(List<BaseItemDto> items) {
if (mCurrentAudioQueue == null) createAudioQueue(items);
else {
int ndx = mCurrentAudioQueue.size();
for (BaseItemDto item : items) {
AudioQueueItem queueItem = new AudioQueueItem(ndx++, item);
mCurrentAudioQueue.add(queueItem);
if (mManagedAudioQueue != null) mManagedAudioQueue.add(queueItem);
}
fireQueueStatusChange();
}
TvApp.getApplication().showMessage(items.size() + (items.size() > 1 ? TvApp.getApplication().getString(R.string.msg_items_added) : TvApp.getApplication().getString(R.string.msg_item_added)), mCurrentAudioQueue.size() + TvApp.getApplication().getString(R.string.msg_total_items_in_queue), 5000, R.drawable.audioicon);
}
public static void removeFromAudioQueue(int ndx) {
if (mCurrentAudioQueuePosition == ndx) {
// current item - stop audio, remove and re-start
stopAudio();
if (mManagedAudioQueue != null) {
mManagedAudioQueue.remove(mCurrentAudioQueue.get(ndx));
}
mCurrentAudioQueue.removeItems(ndx, 1);
mCurrentAudioQueuePosition--;
mCurrentAudioPosition = 0;
if (ndx >= 0 && ndx < mCurrentAudioQueue.size()) {
nextAudioItem();
} else {
if (mCurrentAudioQueuePosition >= 0) mCurrentAudioItem = ((BaseRowItem)mCurrentAudioQueue.get(mCurrentAudioQueuePosition)).getBaseItem();
// fire a change to update current item
fireQueueStatusChange();
}
} else {
//just remove it
mCurrentAudioQueue.removeItems(ndx, 1);
if (mManagedAudioQueue != null) {
mManagedAudioQueue.remove(mManagedAudioQueue.findByIndex(ndx));
}
}
// now need to update indexes for subsequent items
if (hasAudioQueueItems()) {
for (int i = ndx; i < mCurrentAudioQueue.size(); i++){
((BaseRowItem)mCurrentAudioQueue.get(i)).setIndex(i);
}
}
}
public static boolean isPlayingAudio() { return audioInitialized && (nativeMode ? mExoplayer.isPlaying() : mVlcPlayer.isPlaying()); }
private static boolean ensureInitialized() {
if (!audioInitialized) {
audioInitialized = initAudio();
}
if (!audioInitialized) {
Utils.showToast(TvApp.getApplication(), "Unable to play audio");
}
return audioInitialized;
}
public static void playNow(final List<BaseItemDto> items) {
if (!ensureInitialized()) return;
if (hasAudioQueueItems()) {
new AlertDialog.Builder(TvApp.getApplication().getCurrentActivity())
.setTitle(R.string.lbl_items_in_queue)
.setMessage(R.string.msg_replace_or_add_queue_q)
.setPositiveButton(R.string.btn_replace_queue, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
playNowInternal(items);
}
})
.setNeutralButton(R.string.lbl_add_to_queue, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
addToAudioQueue(items);
}
})
.setNegativeButton(R.string.lbl_cancel, null)
.show();
} else {
playNowInternal(items);
}
}
private static void playNowInternal(List<BaseItemDto> items) {
createAudioQueue(items);
mCurrentAudioQueuePosition = -1;
nextAudioItem();
if (TvApp.getApplication().getCurrentActivity().getClass() != AudioNowPlayingActivity.class) {
Intent nowPlaying = new Intent(TvApp.getApplication(), AudioNowPlayingActivity.class);
TvApp.getApplication().getCurrentActivity().startActivity(nowPlaying);
} else {
TvApp.getApplication().showMessage(items.size() + (items.size() > 1 ? TvApp.getApplication().getString(R.string.msg_items_added) : TvApp.getApplication().getString(R.string.msg_item_added)), mCurrentAudioQueue.size() + TvApp.getApplication().getString(R.string.msg_total_items_in_queue), 5000, R.drawable.audioicon);
}
}
public static void playNow(final BaseItemDto item) {
if (!ensureInitialized()) return;
List<BaseItemDto> list = new ArrayList<BaseItemDto>();
list.add(item);
playNow(list);
}
public static boolean playFrom(int ndx) {
if (ndx >= mCurrentAudioQueue.size()) return false;
if (isPlayingAudio()) stopAudio();
mCurrentAudioQueuePosition = ndx-1;
createManagedAudioQueue();
nextAudioItem();
return true;
}
private static boolean ensureAudioFocus() {
if (mAudioManager.requestAudioFocus(mAudioFocusChanged, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
TvApp.getApplication().getLogger().Error("Unable to get audio focus");
Utils.showToast(TvApp.getApplication(), R.string.msg_cannot_play_time);
return false;
}
//Register a media button receiver so that all media button presses will come to us and not another app
mAudioManager.registerMediaButtonEventReceiver(new ComponentName(TvApp.getApplication().getPackageName(), RemoteControlReceiver.class.getName()));
//TODO implement conditional logic for api 21+
return true;
}
private static void playInternal(final BaseItemDto item, final int pos) {
if (!ensureInitialized()) return;
ensureAudioFocus();
final ApiClient apiClient = TvApp.getApplication().getApiClient();
AudioOptions options = new AudioOptions();
options.setDeviceId(apiClient.getDeviceId());
options.setItemId(item.getId());
options.setMaxBitrate(TvApp.getApplication().getAutoBitrate());
options.setMediaSources(item.getMediaSources());
DeviceProfile profile = ProfileHelper.getBaseProfile();
if (Utils.is60()) {
ProfileHelper.setExoOptions(profile, false, true);
} else {
ProfileHelper.setVlcOptions(profile);
}
options.setProfile(profile);
TvApp.getApplication().getPlaybackManager().getAudioStreamInfo(apiClient.getServerInfo().getId(), options, false, apiClient, new Response<StreamInfo>() {
@Override
public void onResponse(StreamInfo response) {
mCurrentAudioItem = item;
mCurrentAudioStreamInfo = response;
mCurrentAudioQueuePosition = pos;
mCurrentAudioPosition = 0;
if (nativeMode) {
mExoplayer.setDataSource(TvApp.getApplication(), Uri.parse(response.ToUrl(apiClient.getApiUrl(), apiClient.getAccessToken())));
mExoplayer.start();
} else {
TvApp.getApplication().getLogger().Info("Playback attempt via VLC of " + response.ToUrl(apiClient.getApiUrl(), apiClient.getAccessToken()));
Media media = new Media(mLibVLC, Uri.parse(response.ToUrl(apiClient.getApiUrl(), apiClient.getAccessToken())));
media.parse();
mVlcPlayer.setMedia(media);
media.release();
mVlcPlayer.play();
}
if (mCurrentAudioQueuePosition == 0) {
//we just started or repeated - re-create managed queue
createManagedAudioQueue();
}
updateCurrentAudioItemPlaying(true);
TvApp.getApplication().setLastMusicPlayback(System.currentTimeMillis());
Utils.ReportStart(item, mCurrentAudioPosition * 10000);
for (AudioEventListener listener : mAudioEventListeners) {
TvApp.getApplication().getLogger().Info("Firing playback state change listener for item start. " + mCurrentAudioItem.getName());
listener.onPlaybackStateChange(PlaybackController.PlaybackState.PLAYING, mCurrentAudioItem);
}
}
@Override
public void onError(Exception exception) {
Utils.showToast(TvApp.getApplication(), "Unable to play audio " + exception.getLocalizedMessage());
}
});
}
public static void shuffleAudioQueue() {
if (!hasAudioQueueItems()) return;
List<BaseItemDto> items = new ArrayList<>();
for(int i = 0; i < mCurrentAudioQueue.size(); i++) {
items.add(((BaseRowItem) mCurrentAudioQueue.get(i)).getBaseItem());
}
Collections.shuffle(items);
playNow(items);
}
public static BaseItemDto getNextAudioItem() {
if (mCurrentAudioQueue == null || mCurrentAudioQueue.size() == 0 || (!mRepeat && mCurrentAudioQueuePosition == mCurrentAudioQueue.size() - 1)) return null;
int ndx = mCurrentAudioQueuePosition+1;
if (ndx >= mCurrentAudioQueue.size()) ndx = 0;
return ((BaseRowItem)mCurrentAudioQueue.get(ndx)).getBaseItem();
}
public static BaseItemDto getPrevAudioItem() {
if (mCurrentAudioQueue == null || mCurrentAudioQueue.size() == 0 || (!mRepeat && mCurrentAudioQueuePosition == 0)) return null;
int ndx = mCurrentAudioQueuePosition-1;
if (ndx < 0) ndx = mCurrentAudioQueue.size() - 1;
return ((BaseRowItem)mCurrentAudioQueue.get(ndx)).getBaseItem();
}
public static boolean hasNextAudioItem() { return mCurrentAudioQueue != null && mCurrentAudioQueue.size() > 0 && (mRepeat || mCurrentAudioQueuePosition < mCurrentAudioQueue.size()-1); }
public static boolean hasPrevAudioItem() { return mCurrentAudioQueue != null && mCurrentAudioQueue.size() > 0 && (mRepeat || mCurrentAudioQueuePosition > 0); }
public static void updateCurrentAudioItemPlaying(boolean playing) {
if (mCurrentAudioQueuePosition < 0) return;
BaseRowItem rowItem = (BaseRowItem) mCurrentAudioQueue.get(mCurrentAudioQueuePosition);
if (rowItem != null) {
rowItem.setIsPlaying(playing);
mCurrentAudioQueue.notifyArrayItemRangeChanged(mCurrentAudioQueuePosition, 1);
if (mManagedAudioQueue != null && mManagedAudioQueue.size() > 0) {
BaseRowItem managedItem = (BaseRowItem) mManagedAudioQueue.get(0);
managedItem.setIsPlaying(playing);
mManagedAudioQueue.notifyArrayItemRangeChanged(0, 1);
}
}
}
public static int nextAudioItem() {
//turn off indicator for current item
if (mCurrentAudioQueuePosition >= 0) {
updateCurrentAudioItemPlaying(false);
}
if (mCurrentAudioQueue == null || mCurrentAudioQueue.size() == 0 || (!mRepeat && mCurrentAudioQueuePosition == mCurrentAudioQueue.size() - 1)) return -1;
stopAudio();
if (mManagedAudioQueue != null && mManagedAudioQueue.size() > 1) {
//don't remove last item as it causes framework crashes
mManagedAudioQueue.removeItems(0, 1);
}
int ndx = mCurrentAudioQueuePosition +1;
if (ndx >= mCurrentAudioQueue.size()) ndx = 0;
playInternal(getNextAudioItem(), ndx);
return ndx;
}
public static int prevAudioItem() {
if (mCurrentAudioQueue == null || (!mRepeat && mCurrentAudioQueue.size() == 0)) return -1;
if (isPlayingAudio() && mCurrentAudioPosition > 10000) {
//just back up to the beginning of current item
if (nativeMode) mExoplayer.seekTo(0);
else mVlcPlayer.setTime(0);
return mCurrentAudioQueuePosition;
}
if ( !mRepeat && mCurrentAudioQueuePosition < 1) {
//nowhere to go
return mCurrentAudioQueuePosition;
}
stopAudio();
int ndx = mCurrentAudioQueuePosition - 1;
if (mManagedAudioQueue != null) {
mManagedAudioQueue.add(0, mCurrentAudioQueue.get(ndx));
}
if (ndx < 0) ndx = mCurrentAudioQueue.size() - 1;
playInternal(getPrevAudioItem(), ndx);
return ndx;
}
private static void stop() {
if (nativeMode) mExoplayer.stopPlayback();
else mVlcPlayer.stop();
}
public static void stopAudio() {
if (mCurrentAudioItem != null && isPlayingAudio()) {
stop();
updateCurrentAudioItemPlaying(false);
Utils.ReportStopped(mCurrentAudioItem, mCurrentAudioStreamInfo, mCurrentAudioPosition*10000);
for (AudioEventListener listener : mAudioEventListeners) {
listener.onPlaybackStateChange(PlaybackController.PlaybackState.IDLE, mCurrentAudioItem);
}
//UnRegister a media button receiver
mAudioManager.unregisterMediaButtonEventReceiver(new ComponentName(TvApp.getApplication().getPackageName(), RemoteControlReceiver.class.getName()));
}
}
private static void pause() {
if (nativeMode) mExoplayer.pause();
else mVlcPlayer.pause();
}
public static void pauseAudio() {
if (mCurrentAudioItem != null && isPlayingAudio()) {
updateCurrentAudioItemPlaying(false);
pause();
Utils.ReportStopped(mCurrentAudioItem, mCurrentAudioStreamInfo, mCurrentAudioPosition * 10000);
for (AudioEventListener listener : mAudioEventListeners) {
listener.onPlaybackStateChange(PlaybackController.PlaybackState.PAUSED, mCurrentAudioItem);
}
//UnRegister a media button receiver
mAudioManager.unregisterMediaButtonEventReceiver(new ComponentName(TvApp.getApplication().getPackageName(), RemoteControlReceiver.class.getName()));
lastProgressReport = System.currentTimeMillis();
}
}
public static void resumeAudio() {
if (mCurrentAudioItem != null) {
ensureAudioFocus();
if (nativeMode) mExoplayer.start();
else mVlcPlayer.play();
updateCurrentAudioItemPlaying(true);
Utils.ReportStart(mCurrentAudioItem, mCurrentAudioPosition * 10000);
for (AudioEventListener listener : mAudioEventListeners) {
listener.onPlaybackStateChange(PlaybackController.PlaybackState.PLAYING, mCurrentAudioItem);
}
} else if (hasAudioQueueItems()) {
//play from start
playInternal(((BaseRowItem)mCurrentAudioQueue.get(0)).getBaseItem(), 0);
}
}
public static void setCurrentMediaPosition(int currentMediaPosition) {
MediaManager.mCurrentMediaPosition = currentMediaPosition;
}
public static BaseRowItem getMediaItem(int pos) {
return mCurrentMediaAdapter != null && mCurrentMediaAdapter.size() > pos ? (BaseRowItem) mCurrentMediaAdapter.get(pos) : null;
}
public static BaseRowItem getCurrentMediaItem() { return getMediaItem(mCurrentMediaPosition); }
public static BaseRowItem nextMedia() {
if (hasNextMediaItem()) {
mCurrentMediaPosition++;
mCurrentMediaAdapter.loadMoreItemsIfNeeded(mCurrentMediaPosition);
}
return getCurrentMediaItem();
}
public static BaseRowItem prevMedia() {
if (hasPrevMediaItem()) {
mCurrentMediaPosition--;
}
return getCurrentMediaItem();
}
public static BaseRowItem peekNextMediaItem() {
return hasNextMediaItem() ? getMediaItem(mCurrentMediaPosition +1) : null;
}
public static BaseRowItem peekPrevMediaItem() {
return hasPrevMediaItem() ? getMediaItem(mCurrentMediaPosition -1) : null;
}
public static boolean hasNextMediaItem() { return mCurrentMediaAdapter.size() > mCurrentMediaPosition +1; }
public static boolean hasPrevMediaItem() { return mCurrentMediaPosition > 0; }
public static String getCurrentMediaTitle() {
return currentMediaTitle;
}
public static void setCurrentMediaTitle(String currentMediaTitle) {
MediaManager.currentMediaTitle = currentMediaTitle;
}
public static boolean isVideoQueueModified() {
return videoQueueModified;
}
public static void setVideoQueueModified(boolean videoQueueModified) {
MediaManager.videoQueueModified = videoQueueModified;
}
public static void clearVideoQueue() {
mCurrentVideoQueue = new ArrayList<>();
videoQueueModified = false;
}
}