MediaControlView.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * @author David A. Velasco
  5. * Copyright (C) 2015 ownCloud Inc.
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License version 2,
  9. * as published by the Free Software Foundation.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.owncloud.android.media;
  20. import android.content.Context;
  21. import android.media.MediaPlayer;
  22. import android.os.Handler;
  23. import android.os.Message;
  24. import android.util.AttributeSet;
  25. import android.view.KeyEvent;
  26. import android.view.LayoutInflater;
  27. import android.view.View;
  28. import android.view.View.OnClickListener;
  29. import android.view.ViewGroup;
  30. import android.view.accessibility.AccessibilityEvent;
  31. import android.view.accessibility.AccessibilityNodeInfo;
  32. import android.widget.FrameLayout;
  33. import android.widget.ImageButton;
  34. import android.widget.MediaController.MediaPlayerControl;
  35. import android.widget.ProgressBar;
  36. import android.widget.SeekBar;
  37. import android.widget.SeekBar.OnSeekBarChangeListener;
  38. import android.widget.TextView;
  39. import com.owncloud.android.R;
  40. import com.owncloud.android.utils.DisplayUtils;
  41. import java.util.Formatter;
  42. import java.util.Locale;
  43. /**
  44. * View containing controls for a {@link MediaPlayer}.
  45. *
  46. * Holds buttons "play / pause", "rewind", "fast forward"
  47. * and a progress slider.
  48. *
  49. * It synchronizes itself with the state of the
  50. * {@link MediaPlayer}.
  51. */
  52. public class MediaControlView extends FrameLayout /* implements OnLayoutChangeListener, OnTouchListener */ implements OnClickListener, OnSeekBarChangeListener {
  53. private MediaPlayerControl mPlayer;
  54. private Context mContext;
  55. private View mRoot;
  56. private ProgressBar mProgress;
  57. private TextView mEndTime, mCurrentTime;
  58. private boolean mDragging;
  59. private static final int SHOW_PROGRESS = 1;
  60. private ImageButton mPauseButton;
  61. private ImageButton mFfwdButton;
  62. private ImageButton mRewButton;
  63. public MediaControlView(Context context, AttributeSet attrs) {
  64. super(context, attrs);
  65. mContext = context;
  66. FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
  67. ViewGroup.LayoutParams.MATCH_PARENT,
  68. ViewGroup.LayoutParams.MATCH_PARENT
  69. );
  70. LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  71. mRoot = inflate.inflate(R.layout.media_control, null);
  72. initControllerView(mRoot);
  73. addView(mRoot, frameParams);
  74. setFocusable(true);
  75. setFocusableInTouchMode(true);
  76. setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  77. requestFocus();
  78. }
  79. @Override
  80. public void onFinishInflate() {
  81. super.onFinishInflate();
  82. /*
  83. if (mRoot != null)
  84. initControllerView(mRoot);
  85. */
  86. }
  87. /* TODO REMOVE
  88. public MediaControlView(Context context, boolean useFastForward) {
  89. super(context);
  90. mContext = context;
  91. mUseFastForward = useFastForward;
  92. initFloatingWindowLayout();
  93. //initFloatingWindow();
  94. }
  95. */
  96. /* TODO REMOVE
  97. public MediaControlView(Context context) {
  98. this(context, true);
  99. }
  100. */
  101. /* T
  102. private void initFloatingWindow() {
  103. mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
  104. mWindow = PolicyManager.makeNewWindow(mContext);
  105. mWindow.setWindowManager(mWindowManager, null, null);
  106. mWindow.requestFeature(Window.FEATURE_NO_TITLE);
  107. mDecor = mWindow.getDecorView();
  108. mDecor.setOnTouchListener(mTouchListener);
  109. mWindow.setContentView(this);
  110. mWindow.setBackgroundDrawableResource(android.R.color.transparent);
  111. // While the media controller is up, the volume control keys should
  112. // affect the media stream type
  113. mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);
  114. setFocusable(true);
  115. setFocusableInTouchMode(true);
  116. setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  117. requestFocus();
  118. }
  119. */
  120. /*
  121. // Allocate and initialize the static parts of mDecorLayoutParams. Must
  122. // also call updateFloatingWindowLayout() to fill in the dynamic parts
  123. // (y and width) before mDecorLayoutParams can be used.
  124. private void initFloatingWindowLayout() {
  125. mDecorLayoutParams = new WindowManager.LayoutParams();
  126. WindowManager.LayoutParams p = mDecorLayoutParams;
  127. p.gravity = Gravity.TOP;
  128. p.height = LayoutParams.WRAP_CONTENT;
  129. p.x = 0;
  130. p.format = PixelFormat.TRANSLUCENT;
  131. p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
  132. p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
  133. | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
  134. | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
  135. p.token = null;
  136. p.windowAnimations = 0; // android.R.style.DropDownAnimationDown;
  137. }
  138. */
  139. // Update the dynamic parts of mDecorLayoutParams
  140. // Must be called with mAnchor != NULL.
  141. /*
  142. private void updateFloatingWindowLayout() {
  143. int [] anchorPos = new int[2];
  144. mAnchor.getLocationOnScreen(anchorPos);
  145. WindowManager.LayoutParams p = mDecorLayoutParams;
  146. p.width = mAnchor.getWidth();
  147. p.y = anchorPos[1] + mAnchor.getHeight();
  148. }
  149. */
  150. /*
  151. // This is called whenever mAnchor's layout bound changes
  152. public void onLayoutChange(View v, int left, int top, int right,
  153. int bottom, int oldLeft, int oldTop, int oldRight,
  154. int oldBottom) {
  155. //updateFloatingWindowLayout();
  156. if (mShowing) {
  157. mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams);
  158. }
  159. }
  160. */
  161. /*
  162. public boolean onTouch(View v, MotionEvent event) {
  163. if (event.getAction() == MotionEvent.ACTION_DOWN) {
  164. if (mShowing) {
  165. hide();
  166. }
  167. }
  168. return false;
  169. }
  170. */
  171. public void setMediaPlayer(MediaPlayerControl player) {
  172. mPlayer = player;
  173. mHandler.sendEmptyMessage(SHOW_PROGRESS);
  174. updatePausePlay();
  175. }
  176. private void initControllerView(View v) {
  177. mPauseButton = (ImageButton) v.findViewById(R.id.playBtn);
  178. if (mPauseButton != null) {
  179. mPauseButton.requestFocus();
  180. mPauseButton.setOnClickListener(this);
  181. }
  182. mFfwdButton = (ImageButton) v.findViewById(R.id.forwardBtn);
  183. if (mFfwdButton != null) {
  184. mFfwdButton.setOnClickListener(this);
  185. }
  186. mRewButton = (ImageButton) v.findViewById(R.id.rewindBtn);
  187. if (mRewButton != null) {
  188. mRewButton.setOnClickListener(this);
  189. }
  190. mProgress = (ProgressBar) v.findViewById(R.id.progressBar);
  191. if (mProgress != null) {
  192. if (mProgress instanceof SeekBar) {
  193. SeekBar seeker = (SeekBar) mProgress;
  194. DisplayUtils.colorPreLollipopHorizontalSeekBar(seeker);
  195. seeker.setOnSeekBarChangeListener(this);
  196. } else {
  197. DisplayUtils.colorPreLollipopHorizontalProgressBar(mProgress);
  198. }
  199. mProgress.setMax(1000);
  200. }
  201. mEndTime = (TextView) v.findViewById(R.id.totalTimeText);
  202. mCurrentTime = (TextView) v.findViewById(R.id.currentTimeText);
  203. }
  204. /**
  205. * Disable pause or seek buttons if the stream cannot be paused or seeked.
  206. * This requires the control interface to be a MediaPlayerControlExt
  207. */
  208. private void disableUnsupportedButtons() {
  209. try {
  210. if (mPauseButton != null && !mPlayer.canPause()) {
  211. mPauseButton.setEnabled(false);
  212. }
  213. if (mRewButton != null && !mPlayer.canSeekBackward()) {
  214. mRewButton.setEnabled(false);
  215. }
  216. if (mFfwdButton != null && !mPlayer.canSeekForward()) {
  217. mFfwdButton.setEnabled(false);
  218. }
  219. } catch (IncompatibleClassChangeError ex) {
  220. // We were given an old version of the interface, that doesn't have
  221. // the canPause/canSeekXYZ methods. This is OK, it just means we
  222. // assume the media can be paused and seeked, and so we don't disable
  223. // the buttons.
  224. }
  225. }
  226. private Handler mHandler = new Handler() {
  227. @Override
  228. public void handleMessage(Message msg) {
  229. int pos;
  230. if (msg.what == SHOW_PROGRESS) {
  231. pos = setProgress();
  232. if (!mDragging) {
  233. msg = obtainMessage(SHOW_PROGRESS);
  234. sendMessageDelayed(msg, 1000 - (pos % 1000));
  235. }
  236. }
  237. }
  238. };
  239. private String stringForTime(int timeMs) {
  240. int totalSeconds = timeMs / 1000;
  241. int seconds = totalSeconds % 60;
  242. int minutes = (totalSeconds / 60) % 60;
  243. int hours = totalSeconds / 3600;
  244. final StringBuilder mFormatBuilder = new StringBuilder();
  245. final Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
  246. if (hours > 0) {
  247. return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
  248. } else {
  249. return mFormatter.format("%02d:%02d", minutes, seconds).toString();
  250. }
  251. }
  252. private int setProgress() {
  253. if (mPlayer == null || mDragging) {
  254. return 0;
  255. }
  256. int position = mPlayer.getCurrentPosition();
  257. int duration = mPlayer.getDuration();
  258. if (mProgress != null) {
  259. if (duration > 0) {
  260. // use long to avoid overflow
  261. long pos = 1000L * position / duration;
  262. mProgress.setProgress((int) pos);
  263. }
  264. int percent = mPlayer.getBufferPercentage();
  265. mProgress.setSecondaryProgress(percent * 10);
  266. }
  267. if (mEndTime != null) {
  268. mEndTime.setText(stringForTime(duration));
  269. }
  270. if (mCurrentTime != null) {
  271. mCurrentTime.setText(stringForTime(position));
  272. }
  273. return position;
  274. }
  275. @Override
  276. public boolean dispatchKeyEvent(KeyEvent event) {
  277. int keyCode = event.getKeyCode();
  278. final boolean uniqueDown = event.getRepeatCount() == 0
  279. && event.getAction() == KeyEvent.ACTION_DOWN;
  280. if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
  281. || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
  282. || keyCode == KeyEvent.KEYCODE_SPACE) {
  283. if (uniqueDown) {
  284. doPauseResume();
  285. //show(sDefaultTimeout);
  286. if (mPauseButton != null) {
  287. mPauseButton.requestFocus();
  288. }
  289. }
  290. return true;
  291. } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
  292. if (uniqueDown && !mPlayer.isPlaying()) {
  293. mPlayer.start();
  294. updatePausePlay();
  295. //show(sDefaultTimeout);
  296. }
  297. return true;
  298. } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
  299. || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
  300. if (uniqueDown && mPlayer.isPlaying()) {
  301. mPlayer.pause();
  302. updatePausePlay();
  303. //show(sDefaultTimeout);
  304. }
  305. return true;
  306. }
  307. //show(sDefaultTimeout);
  308. return super.dispatchKeyEvent(event);
  309. }
  310. public void updatePausePlay() {
  311. if (mRoot == null || mPauseButton == null) {
  312. return;
  313. }
  314. if (mPlayer.isPlaying()) {
  315. mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
  316. } else {
  317. mPauseButton.setImageResource(android.R.drawable.ic_media_play);
  318. }
  319. }
  320. private void doPauseResume() {
  321. if (mPlayer.isPlaying()) {
  322. mPlayer.pause();
  323. } else {
  324. mPlayer.start();
  325. }
  326. updatePausePlay();
  327. }
  328. @Override
  329. public void setEnabled(boolean enabled) {
  330. if (mPauseButton != null) {
  331. mPauseButton.setEnabled(enabled);
  332. }
  333. if (mFfwdButton != null) {
  334. mFfwdButton.setEnabled(enabled);
  335. }
  336. if (mRewButton != null) {
  337. mRewButton.setEnabled(enabled);
  338. }
  339. if (mProgress != null) {
  340. mProgress.setEnabled(enabled);
  341. }
  342. disableUnsupportedButtons();
  343. super.setEnabled(enabled);
  344. }
  345. @Override
  346. public void onClick(View v) {
  347. int pos;
  348. boolean playing = mPlayer.isPlaying();
  349. switch (v.getId()) {
  350. case R.id.playBtn:
  351. doPauseResume();
  352. break;
  353. case R.id.rewindBtn:
  354. pos = mPlayer.getCurrentPosition();
  355. pos -= 5000;
  356. mPlayer.seekTo(pos);
  357. if (!playing) {
  358. mPlayer.pause(); // necessary in some 2.3.x devices
  359. }
  360. setProgress();
  361. break;
  362. case R.id.forwardBtn:
  363. pos = mPlayer.getCurrentPosition();
  364. pos += 15000;
  365. mPlayer.seekTo(pos);
  366. if (!playing) {
  367. mPlayer.pause(); // necessary in some 2.3.x devices
  368. }
  369. setProgress();
  370. break;
  371. }
  372. }
  373. @Override
  374. public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
  375. if (!fromUser) {
  376. // We're not interested in programmatically generated changes to
  377. // the progress bar's position.
  378. return;
  379. }
  380. long duration = mPlayer.getDuration();
  381. long newposition = (duration * progress) / 1000L;
  382. mPlayer.seekTo((int) newposition);
  383. if (mCurrentTime != null) {
  384. mCurrentTime.setText(stringForTime((int) newposition));
  385. }
  386. }
  387. /**
  388. * Called in devices with touchpad when the user starts to adjust the
  389. * position of the seekbar's thumb.
  390. *
  391. * Will be followed by several onProgressChanged notifications.
  392. */
  393. @Override
  394. public void onStartTrackingTouch(SeekBar seekBar) {
  395. mDragging = true; // monitors the duration of dragging
  396. mHandler.removeMessages(SHOW_PROGRESS); // grants no more updates with media player progress while dragging
  397. }
  398. /**
  399. * Called in devices with touchpad when the user finishes the
  400. * adjusting of the seekbar.
  401. */
  402. @Override
  403. public void onStopTrackingTouch(SeekBar seekBar) {
  404. mDragging = false;
  405. setProgress();
  406. updatePausePlay();
  407. mHandler.sendEmptyMessage(SHOW_PROGRESS); // grants future updates with media player progress
  408. }
  409. @Override
  410. public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
  411. super.onInitializeAccessibilityEvent(event);
  412. event.setClassName(MediaControlView.class.getName());
  413. }
  414. @Override
  415. public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
  416. super.onInitializeAccessibilityNodeInfo(info);
  417. info.setClassName(MediaControlView.class.getName());
  418. }
  419. }