package org.edx.mobile.view;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Toast;
import org.edx.mobile.R;
import org.edx.mobile.base.BaseFragmentActivity;
import org.edx.mobile.logger.Logger;
import org.edx.mobile.model.api.EnrolledCoursesResponse;
import org.edx.mobile.model.api.LectureModel;
import org.edx.mobile.model.api.TranscriptModel;
import org.edx.mobile.model.course.VideoBlockModel;
import org.edx.mobile.model.db.DownloadEntry;
import org.edx.mobile.module.db.DataCallback;
import org.edx.mobile.module.db.impl.DatabaseFactory;
import org.edx.mobile.player.IPlayerEventCallback;
import org.edx.mobile.player.PlayerFragment;
import org.edx.mobile.services.ViewPagerDownloadManager;
import org.edx.mobile.util.AppConstants;
import org.edx.mobile.util.MediaConsentUtils;
import org.edx.mobile.util.NetworkUtil;
import org.edx.mobile.view.dialog.IDialogCallback;
import java.io.File;
public class CourseUnitVideoFragment extends CourseUnitFragment
implements IPlayerEventCallback{
protected final Logger logger = new Logger(getClass().getName());
VideoBlockModel unit;
private PlayerFragment playerFragment;
private boolean myVideosFlag = false;
private boolean isActivityStarted;
private static final int MSG_UPDATE_PROGRESS = 1022;
private String chapterName;
private LectureModel lecture;
private EnrolledCoursesResponse enrollment;
private DownloadEntry videoModel;
private Runnable playPending;
private final Handler playHandler = new Handler();
private View messageContainer;
private final static String HAS_NEXT_UNIT_ID = "has_next_unit";
private boolean hasNextUnit;
private final static String HAS_PREV_UNIT_ID = "has_prev_unit";
private boolean hasPreviousUnit;
/**
* Create a new instance of fragment
*/
public static CourseUnitVideoFragment newInstance(VideoBlockModel unit, boolean hasNextUnit, boolean hasPreviousUnit) {
CourseUnitVideoFragment f = new CourseUnitVideoFragment();
// Supply num input as an argument.
Bundle args = new Bundle();
args.putSerializable(Router.EXTRA_COURSE_UNIT, unit);
args.putBoolean(HAS_NEXT_UNIT_ID, hasNextUnit);
args.putBoolean(HAS_PREV_UNIT_ID, hasPreviousUnit);
f.setArguments(args);
return f;
}
/**
* When creating, retrieve this instance's number from its arguments.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
unit = getArguments() == null ? null :
(VideoBlockModel) getArguments().getSerializable(Router.EXTRA_COURSE_UNIT);
hasNextUnit = getArguments().getBoolean(HAS_NEXT_UNIT_ID);
hasPreviousUnit = getArguments().getBoolean(HAS_PREV_UNIT_ID);
}
/**
* The Fragment's UI is just a simple text view showing its
* instance number.
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_course_unit_video, container, false);
messageContainer = v.findViewById(R.id.message_container);
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
restore(savedInstanceState);
Intent extraIntent = getActivity().getIntent();
if(extraIntent!=null){
if (extraIntent.hasExtra("FromMyVideos")) {
myVideosFlag = extraIntent.getBooleanExtra(
"FromMyVideos", false);
}
// read incoming chapter name
if (chapterName == null) {
chapterName = extraIntent.getStringExtra("chapter");
}
// read incoming lecture model
if (lecture == null) {
lecture = (LectureModel) extraIntent
.getSerializableExtra("lecture");
}
// read incoming enrollment model
if (enrollment == null) {
enrollment = (EnrolledCoursesResponse) extraIntent
.getSerializableExtra(Router.EXTRA_COURSE_DATA);
}
}
if (chapterName == null) {
if (enrollment != null && lecture != null) {
if (lecture.chapter != null) {
chapterName = lecture.chapter.chapter;
}
}
}
if (playerFragment == null) {
playerFragment = new PlayerFragment();
playerFragment.setCallback(this);
final CourseUnitVideoFragment.HasComponent hasComponent = (CourseUnitVideoFragment.HasComponent)getActivity();
if (hasComponent != null) {
View.OnClickListener next = null;
View.OnClickListener prev = null;
if (hasNextUnit) {
next = new View.OnClickListener() {
@Override
public void onClick(View v) {
hasComponent.navigateNextComponent();
}
};
}
if (hasPreviousUnit) {
prev = new View.OnClickListener() {
@Override
public void onClick(View v) {
hasComponent.navigatePreviousComponent();
}
};
}
playerFragment.setNextPreviousListeners(next, prev);
}
try{
FragmentManager fm = getChildFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.player_container, playerFragment, "player");
ft.commit();
}catch(Exception ex){
logger.error(ex);
}
}
if (getUserVisibleHint()) {
checkVideoStatusAndPlay(unit);
}
if (ViewPagerDownloadManager.instance.inInitialPhase(unit)) {
ViewPagerDownloadManager.instance.addTask(this);
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (playerFragment == null) {
return;
}
if (isVisibleToUser) {
if (playerFragment.getPlayingVideo() == null) {
checkVideoStatusAndPlay(unit);
} else {
checkVideoStatus(unit);
}
} else {
((BaseFragmentActivity) getActivity()).hideInfoMessage();
}
playerFragment.setUserVisibleHint(isVisibleToUser);
}
@Override
public void run() {
ViewPagerDownloadManager.instance.done(this, false);
}
private boolean checkDownloadEntry(DownloadEntry entry) {
if (entry == null || !entry.isDownload()) {
return false;
}
if (entry.isVideoForWebOnly) {
Toast.makeText(getContext(), getString(R.string.video_only_on_web_short),
Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
private void checkVideoStatus(VideoBlockModel unit) {
final DownloadEntry entry = unit.getDownloadEntry(environment.getStorage());
if (checkDownloadEntry(entry) && !entry.isDownloaded()) {
if (!MediaConsentUtils.canStreamMedia(getContext())) {
((BaseFragmentActivity) getActivity()).
showInfoMessage(getString(R.string.wifi_off_message));
}
}
}
private void checkVideoStatusAndPlay(VideoBlockModel unit) {
final DownloadEntry entry = unit.getDownloadEntry(environment.getStorage());
if (!checkDownloadEntry(entry)) return;
if (entry.isDownloaded()) {
startOnlinePlay(entry);
} else {
MediaConsentUtils.requestStreamMedia(getActivity(), new IDialogCallback() {
@Override
public void onPositiveClicked() {
startOnlinePlay(entry);
}
@Override
public void onNegativeClicked() {
((BaseFragmentActivity) getActivity()).
showInfoMessage(getString(R.string.wifi_off_message));
notifyAdapter();
}
});
}
}
private void startOnlinePlay(DownloadEntry model){
if ( !isPlayerVisible()) {
// don't try to showPlayer() if already shown here
// this will cause player to freeze
showPlayer();
}
addVideoDatatoDb(model);
playVideoModel(model);
notifyAdapter();
}
public synchronized void playVideoModel(final DownloadEntry video) {
try {
if (playerFragment.isPlaying()) {
if (video.getVideoId().equals(playerFragment.getPlayingVideo().getVideoId())) {
logger.debug("this video is already being played, skipping play event");
return;
}
}
} catch(Exception ex) {
logger.debug(ex.toString());
}
try{
// reload this model
environment.getStorage().reloadDownloadEntry(video);
logger.debug("Resumed= " + playerFragment.isResumed());
if ( !playerFragment.isResumed()) {
// playback can work only if fragment is resume
if (playPending != null) {
playHandler.removeCallbacks(playPending);
}
playPending = new Runnable() {
public void run() {
playVideoModel(video);
}
};
playHandler.postDelayed(playPending, 200);
return;
} else {
if (playPending != null) {
playHandler.removeCallbacks(playPending);
}
}
TranscriptModel transcript = getTranscriptModel(video);
String filepath = getVideoPath(video);
playerFragment.prepare(filepath, video.lastPlayedOffset,
video.getTitle(), transcript, video);
try {
// capture chapter name
if (chapterName == null) {
// capture the chapter name of this video
chapterName = video.chapter;
}
videoModel = video;
} catch (Exception e) {
logger.error(e);
}
}catch(Exception ex){
logger.error(ex);
}
}
private String getVideoPath(DownloadEntry video){
String filepath = null;
if (!(video.filepath != null && video.filepath.length()>0)) {
if (video.isDownloaded()) {
File f = new File(video.filepath);
if (f.exists()) {
// play from local
filepath = video.filepath;
logger.debug("playing from local file");
}
}
} else {
DownloadEntry de = (DownloadEntry)DatabaseFactory.getInstance( DatabaseFactory.TYPE_DATABASE_NATIVE )
.getIVideoModelByVideoUrl(
video.url, null);
if(de!=null){
if(de.filepath!=null){
File f = new File(de.filepath);
if (f.exists()) {
// play from local
filepath = de.filepath;
logger.debug("playing from local file for " +
"another Download Entry");
}
}
}
}
if(TextUtils.isEmpty(filepath)){
// not available on local, so play online
logger.warn("Local file path not available");
filepath = video.getBestEncodingUrl(getActivity());
}
return filepath;
}
private TranscriptModel getTranscriptModel(DownloadEntry video){
TranscriptModel transcript = null;
if(unit!=null && unit.getData() != null &&
unit.getData().transcripts != null) {
transcript = unit.getData().transcripts;
}
if ( transcript == null ) {
try {
if (video.videoId != null) {
transcript = environment.getServiceManager().getTranscriptsOfVideo(video.eid, video.videoId);
}
} catch (Exception e) {
logger.error(e);
}
}
return transcript;
}
private void showPlayer() {
try {
if(getView()!=null){
View container = getView().findViewById(R.id.player_container);
if (container != null) {
container.setVisibility(View.VISIBLE);
}
}
} catch (Exception ex) {
logger.error(ex);
logger.warn("Error in showing player");
}
}
@Override
public void onStop() {
super.onStop();
isActivityStarted = false;
AppConstants.videoListDeleteMode = false;
try {
if (playerFragment != null) {
playerFragment.onStop();
}
} catch (Exception ex) {
logger.error(ex);
}
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onStart() {
super.onStart();
isActivityStarted = true;
if (!myVideosFlag) {
handler.sendEmptyMessage(MSG_UPDATE_PROGRESS);
}
}
@Override
public void onResume() {
super.onResume();
updateUIForOrientation();
}
public boolean isActivityStarted() {
return isActivityStarted;
}
private final Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == MSG_UPDATE_PROGRESS) {
if (isActivityStarted()) {
if (NetworkUtil.isConnected(getActivity())) {
sendEmptyMessageDelayed(MSG_UPDATE_PROGRESS, 3000);
}
}
}
}
};
public void markPlaying() {
environment.getStorage().markVideoPlaying(videoModel, watchedStateCallback);
}
/**
* This method inserts the Download Entry Model in the database
* Called when a user clicks on a Video in the list
* @param v - Download Entry object
*/
public void addVideoDatatoDb(final DownloadEntry v) {
try {
if (v != null) {
DatabaseFactory.getInstance(DatabaseFactory.TYPE_DATABASE_NATIVE).addVideoData(v, new DataCallback<Long>() {
@Override
public void onResult(Long result) {
if (result != -1) {
logger.debug("Video entry inserted" + v.videoId);
}
}
@Override
public void onFail(Exception ex) {
logger.error(ex);
}
});
}
} catch (Exception ex) {
logger.error(ex);
}
}
public void saveCurrentPlaybackPosition(int offset) {
try {
DownloadEntry v = videoModel;
if (v != null) {
// mark this as partially watches, as playing has started
DatabaseFactory.getInstance( DatabaseFactory.TYPE_DATABASE_NATIVE ).updateVideoLastPlayedOffset(v.videoId, offset,
setCurrentPositionCallback);
}
} catch (Exception ex) {
logger.error(ex);
}
}
@Override
public void onError() {
}
@Override
public void onPlaybackStarted() {
markPlaying();
}
public void onPlaybackComplete() {
try {
DownloadEntry v = videoModel;
if (v!=null && v.watched == DownloadEntry.WatchedState.PARTIALLY_WATCHED) {
videoModel.watched = DownloadEntry.WatchedState.WATCHED;
// mark this as partially watches, as playing has started
DatabaseFactory.getInstance( DatabaseFactory.TYPE_DATABASE_NATIVE )
.updateVideoWatchedState(v.videoId, DownloadEntry.WatchedState.WATCHED,
watchedStateCallback);
}
} catch (Exception ex) {
logger.error(ex);
}
}
private boolean isPlayerVisible() {
if (getActivity() == null) {
return false;
}
View container = getActivity().findViewById(R.id.container_player);
return (container != null && container.getVisibility() == View.VISIBLE);
}
public void notifyAdapter() {
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putSerializable("model", videoModel);
super.onSaveInstanceState(outState);
}
private void restore(Bundle savedInstanceState) {
if (savedInstanceState != null) {
videoModel = (DownloadEntry) savedInstanceState.getSerializable("model");
}
}
private DataCallback<Integer> watchedStateCallback = new DataCallback<Integer>() {
@Override
public void onResult(Integer result) {
logger.debug("Watched State Updated");
}
@Override
public void onFail(Exception ex) {
logger.error(ex);
}
};
private DataCallback<Integer> setCurrentPositionCallback = new DataCallback<Integer>() {
@Override
public void onResult(Integer result) {
logger.debug("Current Playback Position Updated");
}
@Override
public void onFail(Exception ex) {
logger.error(ex);
}
};
private void updateUIForOrientation() {
//TODO - should we use load different layout file?
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
messageContainer.setVisibility(View.GONE);
LinearLayout playerContainer = (LinearLayout)getView().findViewById(R.id.player_container);
if ( playerContainer != null ) {
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
float screenHeight = displayMetrics.heightPixels;
playerContainer.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, (int) screenHeight));
playerContainer.requestLayout();
}
} else {
messageContainer.setVisibility(View.VISIBLE);
LinearLayout playerContainer = (LinearLayout)getView().findViewById(R.id.player_container);
if ( playerContainer != null ) {
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
float screenWidth = displayMetrics.widthPixels;
float ideaHeight = screenWidth * 9 / 16;
playerContainer.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, (int) ideaHeight));
playerContainer.requestLayout();
}
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateUIForOrientation();
}
}