/******************************************************************************
* Copyright (C) 2012, 2013, 2014, 2015, 2016
* Younghyung Cho. <yhcting77@gmail.com>
* All rights reserved.
*
* This file is part of NetMBuddy
*
* This program is licensed under the FreeBSD license
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of the FreeBSD Project.
*****************************************************************************/
package free.yhc.netmbuddy.core;
import java.util.LinkedHashSet;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.TextView;
import free.yhc.abaselib.AppEnv;
import free.yhc.baselib.Logger;
import free.yhc.baselib.async.Task;
import free.yhc.abaselib.util.AUtil;
import free.yhc.abaselib.ux.DialogTask;
import free.yhc.netmbuddy.Err;
import free.yhc.netmbuddy.R;
import free.yhc.netmbuddy.db.ColVideo;
import free.yhc.netmbuddy.db.DB;
import free.yhc.netmbuddy.core.YTPlayer.DBUpdateType;
import free.yhc.netmbuddy.core.YTPlayer.Video;
import free.yhc.netmbuddy.db.DMVideo;
import free.yhc.netmbuddy.utils.Util;
import free.yhc.netmbuddy.utils.UxUtil;
import free.yhc.netmbuddy.utils.YTUtil;
import free.yhc.netmbuddy.widget.SlidingDrawer;
import static free.yhc.abaselib.AppEnv.getUiHandler;
import static free.yhc.abaselib.util.AUtil.isUiThread;
import static free.yhc.abaselib.util.UxUtil.showTextToast;
import static free.yhc.abaselib.util.UxUtil.ConfirmAction;
public class YTPlayerUI implements
OnSharedPreferenceChangeListener {
private static final boolean DBG = Logger.DBG_DEFAULT;
private static final Logger P = Logger.create(YTPlayerUI.class, Logger.LOGLV_DEFAULT);
private static final int SEEKBAR_MAX = 1000;
private static YTPlayerUI sInstance = null;
// ------------------------------------------------------------------------
//
// ------------------------------------------------------------------------
private final Resources mRes = AppEnv.getAppContext().getResources();
private final DB mDb = DB.get();
private final UpdateProgress mUpdateProg = new UpdateProgress();
private final YTPlayer mMp;
private final TimeTickReceiver mTTRcvr = new TimeTickReceiver();
// ------------------------------------------------------------------------
// UI Control.
// ------------------------------------------------------------------------
private Activity mVActivity = null;
private LinkedHashSet<YTPlayer.OnDBUpdatedListener> mDbUpdatedListenerl = new LinkedHashSet<>();
private LinearLayout mPlayerv = null;
private LinearLayout mPlayerLDrawer = null;
// To support video
private SurfaceView mSurfacev = null;
// For extra Button
private YTPlayer.ToolButton mToolBtn = null;
///////////////////////////////////////////////////////////////////////////
//
//
//
///////////////////////////////////////////////////////////////////////////
private final YTPlayer.PlayerStateListener mPlayerStateListener = new YTPlayer.PlayerStateListener() {
@Override
public void
onStateChanged(YTPlayer.MPState from, int fromFlag,
YTPlayer.MPState to, int toFlag) {
pvConfigureAll(mPlayerv, mPlayerLDrawer, from, fromFlag, to, toFlag);
notiConfigure(from, fromFlag, to, toFlag);
}
@Override
public void
onBufferingChanged(int percent) {
mUpdateProg.updateSecondary(percent);
}
};
private final YTPlayer.VideosStateListener mVideoStateListener = new YTPlayer.VideosStateListener() {
@Override
public void
onStopped(YTPlayer.StopState state) {
boolean needToNotification = true;
CharSequence msg = "";
switch (state) {
case DONE:
needToNotification = false;
msg = mRes.getText(R.string.msg_playing_done);
break;
case FORCE_STOPPED:
needToNotification = false;
msg = mRes.getText(R.string.msg_playing_stopped);
break;
case NETWORK_UNAVAILABLE:
msg = mRes.getText(R.string.err_network_unavailable);
break;
case UNKNOWN_ERROR:
msg = mRes.getText(R.string.msg_playing_err_unknown);
break;
}
if (null != mPlayerv) {
TextView titlev = (TextView)mPlayerv.findViewById(R.id.mplayer_title);
pvSetTitle(titlev, mRes.getText(R.string.msg_playing_done));
}
if (needToNotification)
NotiManager.get().putPlayerNotification(NotiManager.NotiType.ALERT, (String)msg);
}
@Override
public void
onStarted() {
if (null != mPlayerv)
pvEnableLDrawer(mPlayerLDrawer);
}
@Override
public void
onPlayQChanged() {
// Control button should be re-checked due to 'next' and 'prev' button.
if (null != mPlayerv)
pvConfigureControl((ViewGroup)mPlayerv.findViewById(R.id.mplayer_control),
YTPlayer.MPState.INVALID, YTPlayer.MPSTATE_FLAG_IDLE,
mMp.playerGetState(), mMp.playerGetStateFlag());
if (null == mPlayerLDrawer)
return;
if (mMp.hasActiveVideo()) {
ListView lv = (ListView)mPlayerLDrawer.findViewById(R.id.mplayer_list);
YTPlayerVideoListAdapter adapter = (YTPlayerVideoListAdapter)lv.getAdapter();
if (null != adapter)
adapter.setVidArray(mMp.getVideoList());
} else
pvDisableLDrawer(mPlayerLDrawer);
}
};
///////////////////////////////////////////////////////////////////////////
//
//
//
///////////////////////////////////////////////////////////////////////////
public static class TimeTickReceiver extends BroadcastReceiver {
private YTPlayerUI _mYtpui = null;
void
setYTPlayerUI(YTPlayerUI ytpui) {
_mYtpui = ytpui;
}
@Override
public void
onReceive(Context context, Intent intent) {
// To minimize time spending in 'Broadcast Receiver'.
getUiHandler().post(new Runnable() {
@Override
public void
run() {
YTPlayer mp = YTPlayer.get();
if (null != _mYtpui)
_mYtpui.updateStatusAutoStopSet(mp.isAutoStopSet(), mp.getAutoStopTime());
}
});
}
}
private enum PlayBtnState {
START,
PAUSE,
STOP,
// Temporal state to notify that user triggers 'pause'.
// This state will be overwritten to the other state immediately
// at state machine that configures control buttons.
USER_PAUSE,
}
private class UpdateProgress implements Runnable {
private static final int UPDATE_INTERVAL_MS = 1000;
private SeekBar _mSeekbar = null;
private TextView _mCurposv = null;
private TextView _mMaxposv = null;
private int _mLastProgress = -1;
private int _mLastSecondaryProgress = -1; // For secondary progress
private void
resetProgressView() {
if (null != _mSeekbar) {
_mMaxposv.setText(Util.secsToMinSecText(mMp.playerGetDuration() / 1000));
update(1, 0);
updateSecondary(0);
}
_mLastProgress = 0;
_mLastSecondaryProgress = 0;
}
@SuppressWarnings("unused")
int
getSecondaryProgressPercent() {
int percent = _mLastSecondaryProgress * 100 / SEEKBAR_MAX;
if (percent > 100)
percent = 100;
if (percent < 0)
percent = 0;
return percent;
}
void
setProgressView(ViewGroup progv) {
P.bug(isUiThread());
P.bug(null != progv.findViewById(R.id.mplayer_progress));
_mCurposv = (TextView)progv.findViewById(R.id.mplayer_curpos);
_mMaxposv = (TextView)progv.findViewById(R.id.mplayer_maxpos);
_mSeekbar = (SeekBar)progv.findViewById(R.id.mplayer_seekbar);
if (null != _mSeekbar) {
_mMaxposv.setText(Util.secsToMinSecText(mMp.playerGetDuration() / 1000));
update(mMp.playerGetDuration(), _mLastProgress);
updateSecondary(_mLastSecondaryProgress);
}
}
void
start() {
//logI("Progress Start");
_mMaxposv.setText(Util.secsToMinSecText(mMp.playerGetDuration() / 1000));
update(mMp.playerGetDuration(), _mLastProgress);
updateSecondary(_mLastSecondaryProgress);
run();
}
void
resume() {
getUiHandler().removeCallbacks(this);
getUiHandler().postDelayed(this, UPDATE_INTERVAL_MS);
}
void
pause() {
getUiHandler().removeCallbacks(this);
}
void
stop() {
//logI("Progress End");
getUiHandler().removeCallbacks(this);
resetProgressView();
}
void
update(int durms, int curms) {
// ignore aDuration.
// Sometimes youtube player returns incorrect duration value!
if (null != _mSeekbar) {
int curPv = (durms > 0)? (int)(curms * (long)SEEKBAR_MAX / durms)
: 0;
_mSeekbar.setProgress(curPv);
_mCurposv.setText(Util.secsToMinSecText(curms / 1000));
_mLastProgress = curPv;
}
}
/**
* Update secondary progress
*/
void
updateSecondary(int percent) {
// Update secondary progress
if (null != _mSeekbar) {
int pv = percent * SEEKBAR_MAX / 100;
_mSeekbar.setSecondaryProgress(pv);
_mLastSecondaryProgress = pv;
}
}
@Override
public void
run() {
update(mMp.playerGetDuration(), mMp.playerGetPosition());
getUiHandler().postDelayed(this, UPDATE_INTERVAL_MS);
}
}
// ========================================================================
//
//
//
// ========================================================================
private void
registerTimeTickReceiver() {
if (null == mVActivity)
return;
mTTRcvr.setYTPlayerUI(this);
IntentFilter filter = new IntentFilter(Intent.ACTION_TIME_TICK);
mVActivity.registerReceiver(mTTRcvr, filter);
}
private void
unregisterTimeTickReceiver() {
if (null == mVActivity)
return;
mVActivity.unregisterReceiver(mTTRcvr);
}
// ========================================================================
//
// Notification Handling
//
// ========================================================================
private void
notiConfigure(@SuppressWarnings("unused") YTPlayer.MPState from,
@SuppressWarnings("unused") int fromFlag,
YTPlayer.MPState to,
@SuppressWarnings("unused") int toFlag) {
NotiManager nm = NotiManager.get();
if (!mMp.hasActiveVideo()) {
nm.removePlayerNotification();
return;
}
String title = mMp.getActiveVideo().v.title;
NotiManager.NotiType ntype;
switch (to) {
case PREPARED:
case PAUSED:
ntype = NotiManager.NotiType.START;
break;
case STARTED:
ntype = NotiManager.NotiType.PAUSE;
break;
case ERROR:
ntype = NotiManager.NotiType.ALERT;
break;
case IDLE:
case INITIALIZED:
case PREPARED_AUDIO:
case PREPARING:
case PLAYBACK_COMPLETED:
ntype = NotiManager.NotiType.STOP;
break;
default:
ntype = NotiManager.NotiType.BASE;
}
nm.putPlayerNotification(ntype, title);
}
// ========================================================================
//
// Player View Handling
//
// ========================================================================
private void
pvDisableButton(ImageView btn) {
btn.setVisibility(View.INVISIBLE);
}
private void
pvEnableButton(ImageView btn, int image) {
if (0 != image)
btn.setImageResource(image);
btn.setVisibility(View.VISIBLE);
}
private void
pvSetTitle(TextView titlev, CharSequence title) {
if (null == titlev || null == title)
return;
titlev.setText(title);
}
private void
pvConfigureTitle(TextView titlev,
@SuppressWarnings("unused") YTPlayer.MPState from,
@SuppressWarnings("unused") int fromFlag,
YTPlayer.MPState to,
@SuppressWarnings("unused") int toFlag) {
if (null == titlev)
return;
CharSequence videoTitle = "";
if (mMp.hasActiveVideo())
videoTitle = mMp.getActiveVideo().v.title;
switch (to) {
case PREPARED:
case PAUSED:
case STARTED:
P.bug(null != videoTitle);
if (mMp.isPlayerBuffering()
|| mMp.isPlayerSeeking())
videoTitle = "(" + mRes.getText(R.string.buffering) + ") " + videoTitle;
pvSetTitle(titlev, videoTitle);
break;
case ERROR:
pvSetTitle(titlev, mRes.getText(R.string.msg_ytplayer_err));
break;
default:
if (Util.isValidValue(videoTitle))
pvSetTitle(titlev, "(" + mRes.getText(R.string.preparing) + ") " + videoTitle);
else
pvSetTitle(titlev, "");
}
}
@SuppressWarnings("unused")
private void
pvDisableControlButton(ViewGroup playerv) {
pvDisableButton((ImageView)playerv.findViewById(R.id.mplayer_btnplay));
pvDisableButton((ImageView)playerv.findViewById(R.id.mplayer_btnnext));
pvDisableButton((ImageView)playerv.findViewById(R.id.mplayer_btnprev));
pvDisableButton((ImageView)playerv.findViewById(R.id.mplayer_btnmore));
pvDisableButton((ImageView)playerv.findViewById(R.id.mplayer_btntool));
}
private void
pvConfigureControl(ViewGroup controlv,
@SuppressWarnings("unused") YTPlayer.MPState from,
@SuppressWarnings("unused") int fromFlag,
YTPlayer.MPState to,
@SuppressWarnings("unused") int toFlag) {
if (null == controlv)
return;
if (!mMp.hasActiveVideo()) {
controlv.setVisibility(View.GONE);
return;
}
controlv.setVisibility(View.VISIBLE);
final ImageView nextv = (ImageView)controlv.findViewById(R.id.mplayer_btnnext);
final ImageView prevv = (ImageView)controlv.findViewById(R.id.mplayer_btnprev);
final ImageView playv = (ImageView)controlv.findViewById(R.id.mplayer_btnplay);
final ImageView morev = (ImageView)controlv.findViewById(R.id.mplayer_btnmore);
final ImageView toolv = (ImageView)controlv.findViewById(R.id.mplayer_btntool);
// --------------------------------------------------------------------
// configure prev/next/more
// --------------------------------------------------------------------
switch (to) {
case PREPARED_AUDIO:
case PREPARED:
case PAUSED:
case STARTED:
pvEnableButton(morev, 0);
// break is missed intentionally.
case IDLE:
case INITIALIZED:
case PREPARING:
if (mMp.hasNextVideo())
pvEnableButton(nextv, 0);
else
pvDisableButton(nextv);
if (mMp.hasPrevVideo())
pvEnableButton(prevv, 0);
else
pvDisableButton(prevv);
break;
default:
pvDisableButton(morev);
pvDisableButton(prevv);
pvDisableButton(nextv);
}
// --------------------------------------------------------------------
// configure play/pause
// --------------------------------------------------------------------
switch (to) {
case PREPARED:
case PAUSED: {
PlayBtnState st = (PlayBtnState)playv.getTag();
if (PlayBtnState.USER_PAUSE == st) {
pvEnableButton(playv, R.drawable.ic_media_stop);
playv.setTag(PlayBtnState.STOP);
// [ Implementing Usecase]
// - Single touch while playing video pauses video.
// - Double-touch while playing video stops videos.
final long sessionId = mMp.getPlayerSessionId();
final Activity activity = mVActivity;
getUiHandler().postDelayed(new Runnable() {
@Override
public void
run() {
// Strict check to know that player UI is still at the same screen and state
// with the moment when user-pause is triggered.
if (mMp.getPlayerSessionId() == sessionId
&& YTPlayer.MPState.PAUSED == mMp.getPlayerState()
&& PlayBtnState.STOP == playv.getTag()
&& activity == mVActivity) {
playv.setImageResource(R.drawable.ic_media_play);
playv.setTag(PlayBtnState.START);
}
}
}, PolicyConstant.YTPLAYER_DOUBLE_TOUCH_INTERVAL);
} else {
pvEnableButton(playv, R.drawable.ic_media_play);
playv.setTag(PlayBtnState.START);
}
} break;
case STARTED:
pvEnableButton(playv, R.drawable.ic_media_pause);
// Set next state be moved to on click as 'Tag'
playv.setTag(PlayBtnState.PAUSE);
break;
case IDLE:
case INITIALIZED:
case PREPARED_AUDIO:
case PREPARING:
case PLAYBACK_COMPLETED:
pvEnableButton(playv, R.drawable.ic_media_stop);
// Set next state be moved to on click as 'Tag'
playv.setTag(PlayBtnState.STOP);
break;
default:
playv.setTag(null);
controlv.setVisibility(View.GONE);
}
// --------------------------------------------------------------------
// configure tool
// --------------------------------------------------------------------
switch (to) {
case INVALID:
case END:
pvDisableButton(toolv);
break;
default:
if (null != mToolBtn)
pvEnableButton(toolv, 0);
}
}
private void
pvConfigureProgress(ViewGroup progressv,
@SuppressWarnings("unused") YTPlayer.MPState from,
@SuppressWarnings("unused") int fromFlag,
YTPlayer.MPState to,
@SuppressWarnings("unused") int toFlag) {
if (null == progressv)
return;
switch (to) {
case PREPARED:
mUpdateProg.start();
break;
case PLAYBACK_COMPLETED:
// Workaround of Youtube player.
// Sometimes Youtube player doesn't update progress 100% before playing is ended.
// So, update to 100% in force at this ended state.
mUpdateProg.update(1, 1);
// Missing 'break' is intentional.
case STARTED:
mUpdateProg.resume();
break;
case PAUSED:
mUpdateProg.pause();
break;
case INITIALIZED:
case PREPARING:
case PREPARED_AUDIO:
// do nothing progress is now under update..
break;
default:
mUpdateProg.stop();
mUpdateProg.update(1, 0);
}
}
private void
pvEnableLDrawer(ViewGroup playerLDrawer) {
if (null == playerLDrawer
|| !mMp.hasActiveVideo())
return; // nothing to do
P.bug(null != mVActivity);
ListView lv = (ListView)playerLDrawer.findViewById(R.id.mplayer_list);
SlidingDrawer drawer = (SlidingDrawer)playerLDrawer.findViewById(R.id.mplayer_ldrawer);
playerLDrawer.setVisibility(View.VISIBLE);
YTPlayerVideoListAdapter adapter = new YTPlayerVideoListAdapter(mVActivity, mMp.getVideoList());
adapter.setActiveItem(mMp.getActiveVideoIndex());
lv.setAdapter(adapter);
drawer.close();
}
private void
pvDisableLDrawer(ViewGroup playerLDrawer) {
if (null == playerLDrawer
|| View.GONE == playerLDrawer.getVisibility())
return; // nothing to do
P.bug(null != mVActivity);
ListView lv = (ListView)playerLDrawer.findViewById(R.id.mplayer_list);
lv.setAdapter(null);
SlidingDrawer drawer = (SlidingDrawer)playerLDrawer.findViewById(R.id.mplayer_ldrawer);
drawer.close();
playerLDrawer.setVisibility(View.GONE);
}
private void
pvConfigureLDrawer(ViewGroup playerLDrawer,
@SuppressWarnings("unused") YTPlayer.MPState from,
@SuppressWarnings("unused") int fromFlag,
@SuppressWarnings("unused") YTPlayer.MPState to,
@SuppressWarnings("unused") int toFlag) {
if (null == playerLDrawer)
return;
if (!mMp.hasActiveVideo()) {
pvDisableLDrawer(playerLDrawer);
return;
}
ListView lv = (ListView)playerLDrawer.findViewById(R.id.mplayer_list);
YTPlayerVideoListAdapter adapter = (YTPlayerVideoListAdapter)lv.getAdapter();
if (null != adapter
&& mMp.getActiveVideoIndex() != adapter.getActiveItemPos()) {
adapter.setActiveItem(mMp.getActiveVideoIndex());
}
}
private void
pvConfigureAll(ViewGroup playerv, ViewGroup playerLDrawer,
YTPlayer.MPState from, int fromFlag,
YTPlayer.MPState to, int toFlag) {
if (null == playerv) {
P.bug(null == playerLDrawer);
return; // nothing to do
}
pvConfigureTitle((TextView)playerv.findViewById(R.id.mplayer_title),
from, fromFlag, to, toFlag);
pvConfigureProgress((ViewGroup)playerv.findViewById(R.id.mplayer_progress),
from, fromFlag, to, toFlag);
pvConfigureControl((ViewGroup)playerv.findViewById(R.id.mplayer_control),
from, fromFlag, to, toFlag);
pvConfigureLDrawer(playerLDrawer, from, fromFlag, to, toFlag);
}
private void
pvMoreControlDetailInfo(long vid) {
UxUtil.showVideoDetailInfo(mVActivity, vid);
}
private void
pvMoreControlSetBookmark(final long vid) {
final int posms = mMp.playerGetPosition();
if (0 == posms) {
showTextToast(R.string.msg_fail_set_bookmark);
return;
}
final String title = AUtil.getResString(R.string.set_bookmark)
+ " : "
+ Util.secsToMinSecText(posms / 1000)
+ AUtil.getResString(R.string.seconds);
UxUtil.EditTextAction action = new UxUtil.EditTextAction() {
@Override
public void
prepare(Dialog dialog, EditText edit) { }
@Override
public void
onOk(Dialog dialog, EditText edit) {
String bmname = edit.getText().toString();
if (bmname.contains("" + DB.BOOKMARK_DELIMITER)) {
String msg = AUtil.getResString(R.string.msg_forbidden_characters) + "\n"
+ " " + DB.BOOKMARK_DELIMITER;
showTextToast(msg);
UxUtil.buildOneLineEditTextDialog(mVActivity,
title,
bmname,
"",
this)
.show();
} else
mDb.addBookmark(vid, bmname, posms);
}
};
UxUtil.buildOneLineEditTextDialogWithHint(mVActivity,
title,
R.string.enter_bookmark_name,
action)
.show();
}
private void
pvMoreControlAddToWithYtid(final UxUtil.OnPostExecuteListener listener,
final Object tag,
final long plid,
final YTPlayer.Video video) {
final int volume;
if (mMp.getActiveVideo() == video)
volume = mMp.playerGetVolume();
else
volume = PolicyConstant.DEFAULT_VIDEO_VOLUME;
Task<Void> t = new Task<Void>() {
private Err
doExecute() {
P.bug(null != video.v);
DMVideo v = new DMVideo();
v.copy(video.v);
P.bug(Util.isValidValue(v.ytvid));
if (!YTUtil.fillYtDataAndThumbnail(v))
return Err.IO_NET;
v.setPreferenceData(volume, "");
DB.Err dberr = DB.get().insertVideoToPlaylist(plid, v);
return Err.map(dberr);
}
@Override
@NonNull
protected Void
doAsync() {
final Err err = doExecute();
AppEnv.getUiHandler().post(new Runnable() {
@Override
public void run() {
listener.onPostExecute(err, tag);
}
});
return null;
}
};
DialogTask.Builder<DialogTask.Builder> b
= new DialogTask.Builder<>(mVActivity, t);
b.setCancelButtonText(R.string.cancel);
b.setMessage(AUtil.getResString(R.string.adding));
if (!b.create().start())
P.bug();
}
private void
pvMoreControlAddTo(Long vid, final Video video) {
final UxUtil.OnPostExecuteListener listener = new UxUtil.OnPostExecuteListener() {
@Override
public void
onPostExecute(Err result, Object tag) {
if (Err.NO_ERR != result)
return;
for (YTPlayer.OnDBUpdatedListener l : mDbUpdatedListenerl)
l.onDbUpdated(DBUpdateType.PLAYLIST);
}
};
if (null != vid) {
UxUtil.addVideosTo(mVActivity,
null,
listener,
UxUtil.PLID_INVALID,
new long[] { vid },
false);
} else {
UxUtil.OnPlaylistSelected action = new UxUtil.OnPlaylistSelected() {
@Override
public void
onPlaylist(long plid, Object tag) {
pvMoreControlAddToWithYtid(listener, tag, plid, video);
}
@Override
public void
onUserMenu(int pos, Object tag) {}
};
// exclude current playlist
UxUtil.buildSelectPlaylistDialog(DB.get(),
mVActivity,
R.string.add_to,
null,
action,
UxUtil.PLID_INVALID,
null)
.show();
}
}
private void
pvMoreControlDelete(final Long vid, final String ytvid) {
final UxUtil.OnPostExecuteListener listener = new UxUtil.OnPostExecuteListener() {
@Override
public void
onPostExecute(Err result, Object tag) {
if (Err.NO_ERR != result)
return;
mMp.removeVideo(ytvid);
if (null != vid) {
for (YTPlayer.OnDBUpdatedListener l : mDbUpdatedListenerl)
l.onDbUpdated(DBUpdateType.PLAYLIST);
}
}
};
if (null == vid) {
ConfirmAction action = new ConfirmAction() {
@Override
public void
onPositive(@NonNull Dialog dialog) {
listener.onPostExecute(Err.NO_ERR, null);
}
@Override
public void
onNegative(@NonNull Dialog dialog) { }
};
UxUtil.buildConfirmDialog(mVActivity,
R.string.delete,
R.string.msg_delete_musics_completely,
action)
.show();
} else {
UxUtil.deleteVideos(mVActivity,
null,
listener,
UxUtil.PLID_UNKNOWN,
new long[] { vid });
}
}
private void
pvOnMoreButtonClicked(@SuppressWarnings("unused") View v) {
final YTPlayer.Video video = mMp.getActiveVideo();
if (null == video)
return; // This line can be reached because of race-condition...
final int[] opts;
final Long vid = (Long)mDb.getVideoInfo(video.v.ytvid, ColVideo.ID);
if (null != vid)
opts = new int[] { R.string.detail_info,
R.string.set_bookmark,
R.string.bookmarks,
R.string.add_to,
R.string.volume,
R.string.delete };
else
opts = new int[] { R.string.add_to,
R.string.volume };
final CharSequence[] items = new CharSequence[opts.length];
for (int i = 0; i < opts.length; i++)
items[i] = AUtil.getResString(opts[i]);
AlertDialog.Builder builder = new AlertDialog.Builder(mVActivity);
builder.setTitle(R.string.player_extra_control_title);
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void
onClick(DialogInterface dialog, int item) {
switch (opts[item]) {
case R.string.detail_info:
// vid is NOT null
//noinspection ConstantConditions
pvMoreControlDetailInfo(vid);
break;
case R.string.set_bookmark:
// vid is NOT null
//noinspection ConstantConditions
pvMoreControlSetBookmark(vid);
break;
case R.string.bookmarks:
UxUtil.showBookmarkDialog(mVActivity, video.v.ytvid, video.v.title);
break;
case R.string.add_to:
pvMoreControlAddTo(vid, video);
break;
case R.string.volume:
changeVideoVolume(video.v.title, video.v.ytvid);
break;
case R.string.delete:
pvMoreControlDelete(vid, video.v.ytvid);
break;
default:
P.bug(false);
}
}
});
builder.create().show();
}
private void
pvSetupControlButton(final ViewGroup playerv) {
ImageView btn = (ImageView)playerv.findViewById(R.id.mplayer_btnplay);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void
onClick(View v) {
// See pvConfigControl() for details.
PlayBtnState st = (PlayBtnState)v.getTag();
if (null == st)
return; // Nothing to do.
switch (st) {
case START:
mMp.playerStart();
break;
case PAUSE:
v.setTag(PlayBtnState.USER_PAUSE);
mMp.playerPause();
break;
case STOP:
// This doesn't means "Stop only this video",
// but means stop playing vidoes - previous user request.
mMp.stopVideos();
break;
default:
// do nothing.
}
}
});
btn = (ImageView)playerv.findViewById(R.id.mplayer_btnprev);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void
onClick(View v) {
mMp.startPrevVideo();
}
});
btn = (ImageView)playerv.findViewById(R.id.mplayer_btnnext);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void
onClick(View v) {
mMp.startNextVideo();
}
});
btn = (ImageView)playerv.findViewById(R.id.mplayer_btnmore);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void
onClick(View v) {
pvOnMoreButtonClicked(v);
}
});
btn = (ImageView)playerv.findViewById(R.id.mplayer_btntool);
if (null == mToolBtn)
btn.setVisibility(View.INVISIBLE);
else {
btn.setImageResource(mToolBtn.drawable);
btn.setOnClickListener(mToolBtn.onClick);
}
}
private void
pvSetupStatusBar(@SuppressWarnings("unused") ViewGroup playerv) {
updateStatusAutoStopSet(mMp.isAutoStopSet(), mMp.getAutoStopTime());
onSharedPreferenceChanged(Util.getSharedPreference(), AUtil.getResString(R.string.csquality));
onSharedPreferenceChanged(Util.getSharedPreference(), AUtil.getResString(R.string.csrepeat));
onSharedPreferenceChanged(Util.getSharedPreference(), AUtil.getResString(R.string.csshuffle));
}
private void
pvInit(ViewGroup playerv,
ViewGroup playerLDrawer,
@SuppressWarnings("unused") SurfaceView surfacev) {
mMp.addPlayerStateListener(mPlayerStateListener);
ViewGroup progv = (ViewGroup)playerv.findViewById(R.id.mplayer_progress);
SeekBar sb = (SeekBar)progv.findViewById(R.id.mplayer_seekbar);
sb.setMax(SEEKBAR_MAX);
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void
onStopTrackingTouch(SeekBar seekBar) {
mMp.playerSeekTo((int)(seekBar.getProgress() * (long)mMp.playerGetDuration() / SEEKBAR_MAX));
}
@Override
public void
onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void
onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
});
mUpdateProg.setProgressView(progv);
if (null != playerLDrawer) {
ListView lv = (ListView)playerLDrawer.findViewById(R.id.mplayer_list);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void
onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (!mMp.hasActiveVideo())
return;
mMp.startVideoAt(position);
}
});
mMp.addVideosStateListener(mVideoStateListener);
final SlidingDrawer drawer = (SlidingDrawer)playerLDrawer.findViewById(R.id.mplayer_ldrawer);
drawer.setOnDrawerOpenListener(new SlidingDrawer.OnDrawerOpenListener() {
@Override
public void
onDrawerOpened() {
if (!mMp.hasActiveVideo()
|| null == mPlayerLDrawer)
return;
ListView lv = (ListView)mPlayerLDrawer.findViewById(R.id.mplayer_list);
int topPos = mMp.getActiveVideoIndex() - 1;
if (topPos < 0)
topPos = 0;
lv.setSelectionFromTop(topPos, 0);
}
});
}
// Enable drawer by default.
// If there is no active video, drawer will be disabled at the configure function.
pvEnableLDrawer(playerLDrawer);
pvSetupStatusBar(playerv);
pvSetupControlButton(playerv);
// Set progress state to right position.
mUpdateProg.update(mMp.playerGetDuration(), mMp.playerGetPosition());
pvConfigureAll(playerv, playerLDrawer,
YTPlayer.MPState.INVALID, YTPlayer.MPSTATE_FLAG_IDLE,
mMp.playerGetState(), mMp.playerGetStateFlag());
}
// ============================================================================
//
// Overrides
//
// ============================================================================
@Override
public void
onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals(AUtil.getResString(R.string.csshuffle))) {
updateStatusShuffle(Util.isPrefSuffle());
} else if (key.equals(AUtil.getResString(R.string.csrepeat))) {
updateStatusRepeat(Util.isPrefRepeat());
} else if (key.equals(AUtil.getResString(R.string.csquality))) {
updateStatusQuality(Util.getPrefQuality());
}
}
// ============================================================================
//
// Update status icons
//
// ============================================================================
void
updateStatusAutoStopSet(boolean set, long timeMillis) {
if (null == mPlayerv)
return;
ImageView iv = (ImageView)mPlayerv.findViewById(R.id.mplayer_status_autostop);
TextView tv = (TextView)mPlayerv.findViewById(R.id.mplayer_status_autostop_time);
if (set) {
long tmLeft = timeMillis - System.currentTimeMillis();
if (tmLeft < 0)
tmLeft = 0;
String tmText = Util.millisToHourMinText(tmLeft) + AUtil.getResString(R.string.minutes);
iv.setImageResource(R.drawable.status_autostop_on);
tv.setVisibility(View.VISIBLE);
tv.setText(tmText);
} else {
iv.setImageResource(R.drawable.status_autostop_off);
tv.setVisibility(View.GONE);
}
}
private void
updateStatusQuality(Util.PrefQuality quality) {
if (null == mPlayerv)
return;
TextView tv = (TextView)mPlayerv.findViewById(R.id.mplayer_status_quality);
CharSequence text = "?";
switch (quality) {
case VERYHIGH:
text = "VH";
break;
case HIGH:
text = "H";
break;
case NORMAL:
text = "M";
break;
case MIDLOW:
text = "ML";
break;
case LOW:
text = "L";
break;
}
tv.setText("[" + text + "]");
}
private void
updateStatusShuffle(boolean set) {
if (null == mPlayerv)
return;
ImageView iv = (ImageView)mPlayerv.findViewById(R.id.mplayer_status_shuffle);
iv.setImageResource(set? R.drawable.status_shuffle_on: R.drawable.status_shuffle_off);
}
private void
updateStatusRepeat(boolean set) {
if (null == mPlayerv)
return;
ImageView iv = (ImageView)mPlayerv.findViewById(R.id.mplayer_status_repeat);
iv.setImageResource(set? R.drawable.status_repeat_on: R.drawable.status_repeat_off);
}
// ============================================================================
//
// Package interfaces (for YTPlayer)
//
// ============================================================================
YTPlayerUI(YTPlayer ytplayer) {
mMp = ytplayer;
if (null == sInstance)
sInstance = this;
else
P.bug(false); // This SHOULD BE SINGLETON (Only YTPlayer has this!)
}
void
addOnDbUpdatedListener(YTPlayer.OnDBUpdatedListener listener) {
mDbUpdatedListenerl.add(listener);
}
void
removeOnDbUpdatedListener(YTPlayer.OnDBUpdatedListener listener) {
mDbUpdatedListenerl.remove(listener);
}
void
setController(Activity activity,
ViewGroup playerv,
ViewGroup playerLDrawer,
SurfaceView surfacev,
YTPlayer.ToolButton toolBtn) {
if (null != mVActivity
&& activity != mVActivity)
unsetController(mVActivity);
Util.getSharedPreference().registerOnSharedPreferenceChangeListener(this);
// update notification by force
notiConfigure(YTPlayer.MPState.INVALID, YTPlayer.MPSTATE_FLAG_IDLE,
mMp.playerGetState(), mMp.playerGetStateFlag());
if (activity == mVActivity && mPlayerv == playerv)
// controller is already set for this context.
// So, nothing to do. just return!
return;
mVActivity = activity;
mDbUpdatedListenerl.clear();
mPlayerv = (LinearLayout)playerv;
mPlayerLDrawer = (LinearLayout)playerLDrawer;
mSurfacev = surfacev;
mToolBtn = toolBtn;
if (null == mPlayerv) {
P.bug(null == mPlayerLDrawer);
return;
}
P.bug(null != mPlayerv.findViewById(R.id.mplayer_layout_magic_id));
registerTimeTickReceiver();
pvInit(playerv, playerLDrawer, surfacev);
}
void
unsetController(Activity activity) {
if (activity == mVActivity) {
Util.getSharedPreference().unregisterOnSharedPreferenceChangeListener(this);
unregisterTimeTickReceiver();
mPlayerv = null;
mVActivity = null;
mDbUpdatedListenerl.clear();
mPlayerLDrawer = null;
mSurfacev = null;
}
}
Activity
getActivity() {
return mVActivity;
}
@SuppressWarnings("unused")
SurfaceView
getSurfaceView() {
return mSurfacev;
}
void
notifyToUser(String msg) {
if (null != mVActivity)
showTextToast(msg);
}
void
setPlayerVisibility(int visibility) {
if (null != mPlayerv)
mPlayerv.setVisibility(visibility);
}
void
updateLDrawerList() {
if (null == mPlayerLDrawer)
return;
ListView lv = (ListView)mPlayerLDrawer.findViewById(R.id.mplayer_list);
YTPlayer.Video[] vs = mMp.getVideoList();
if (null == vs)
vs = new YTPlayer.Video[0];
YTPlayerVideoListAdapter adapter = new YTPlayerVideoListAdapter(mVActivity, vs);
adapter.setActiveItem(mMp.getActiveVideoIndex());
lv.setAdapter(adapter);
}
void
changeVideoVolume(final String title, final String ytvid) {
if (null == mVActivity)
return;
final boolean runningVideo;
// Retrieve current volume
int curvol = PolicyConstant.DEFAULT_VIDEO_VOLUME;
if (mMp.isVideoPlaying()
&& mMp.getActiveVideo().v.ytvid.equals(ytvid)) {
runningVideo = true;
curvol = mMp.playerGetVolume();
} else {
runningVideo = false;
Long i = (Long)mDb.getVideoInfo(ytvid, ColVideo.VOLUME);
if (null != i)
curvol = i.intValue();
}
ViewGroup diagv = (ViewGroup)AUtil.inflateLayout(R.layout.mplayer_vol_dialog);
AlertDialog.Builder bldr = new AlertDialog.Builder(mVActivity);
bldr.setView(diagv);
bldr.setTitle(AppEnv.getAppContext().getResources().getText(R.string.volume)
+ " : " + title);
final AlertDialog aDiag = bldr.create();
final SeekBar sbar = (SeekBar)diagv.findViewById(R.id.seekbar);
sbar.setMax(100);
sbar.setProgress(curvol);
sbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void
onStopTrackingTouch(SeekBar seekBar) { }
@Override
public void
onStartTrackingTouch(SeekBar seekBar) { }
@Override
public void
onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (runningVideo)
mMp.playerSetVolume(progress);
}
});
final int oldVolume = curvol;
aDiag.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void
onDismiss(DialogInterface dialog) {
int newVolume = sbar.getProgress();
if (oldVolume == newVolume)
return;
// Save to database and update adapter
// NOTE
// Should I consider about performance?
// Not yet. do something when performance is issued.
mDb.updateVideoVolume(ytvid, newVolume);
}
});
aDiag.show();
}
}