MediaControlView.java 15 KB

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