Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.supportv4.media;
     18 
     19 import android.support.v4.media.TransportController;
     20 import android.support.v4.media.TransportMediator;
     21 import android.support.v4.media.TransportStateListener;
     22 import com.example.android.supportv4.R;
     23 
     24 import android.content.Context;
     25 import android.util.AttributeSet;
     26 import android.view.LayoutInflater;
     27 import android.view.View;
     28 import android.view.accessibility.AccessibilityEvent;
     29 import android.view.accessibility.AccessibilityNodeInfo;
     30 import android.widget.FrameLayout;
     31 import android.widget.ImageButton;
     32 import android.widget.ProgressBar;
     33 import android.widget.SeekBar;
     34 import android.widget.TextView;
     35 
     36 import java.util.Formatter;
     37 import java.util.Locale;
     38 
     39 /**
     40  * Helper for implementing media controls in an application.
     41  * Use instead of the very useful android.widget.MediaController.
     42  * This version is embedded inside of an application's layout.
     43  */
     44 public class MediaController extends FrameLayout {
     45 
     46     private TransportController mController;
     47     private Context mContext;
     48     private ProgressBar mProgress;
     49     private TextView mEndTime, mCurrentTime;
     50     private boolean mDragging;
     51     private boolean mUseFastForward;
     52     private boolean mListenersSet;
     53     private boolean mShowNext, mShowPrev;
     54     private View.OnClickListener mNextListener, mPrevListener;
     55     StringBuilder mFormatBuilder;
     56     Formatter mFormatter;
     57     private ImageButton mPauseButton;
     58     private ImageButton mFfwdButton;
     59     private ImageButton mRewButton;
     60     private ImageButton mNextButton;
     61     private ImageButton mPrevButton;
     62 
     63     private TransportStateListener mStateListener = new TransportStateListener() {
     64         @Override
     65         public void onPlayingChanged(TransportController controller) {
     66             updatePausePlay();
     67         }
     68         @Override
     69         public void onTransportControlsChanged(TransportController controller) {
     70             updateButtons();
     71         }
     72     };
     73 
     74     public MediaController(Context context, AttributeSet attrs) {
     75         super(context, attrs);
     76         mContext = context;
     77         mUseFastForward = true;
     78         LayoutInflater inflate = (LayoutInflater)
     79                 mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
     80         inflate.inflate(R.layout.media_controller, this, true);
     81         initControllerView();
     82     }
     83 
     84     public MediaController(Context context, boolean useFastForward) {
     85         super(context);
     86         mContext = context;
     87         mUseFastForward = useFastForward;
     88     }
     89 
     90     public MediaController(Context context) {
     91         this(context, true);
     92     }
     93 
     94     public void setMediaPlayer(TransportController controller) {
     95         if (getWindowToken() != null) {
     96             if (mController != null) {
     97                 mController.unregisterStateListener(mStateListener);
     98             }
     99             if (controller != null) {
    100                 controller.registerStateListener(mStateListener);
    101             }
    102         }
    103         mController = controller;
    104         updatePausePlay();
    105     }
    106 
    107     @Override
    108     protected void onAttachedToWindow() {
    109         super.onAttachedToWindow();
    110         if (mController != null) {
    111             mController.registerStateListener(mStateListener);
    112         }
    113     }
    114 
    115     @Override
    116     protected void onDetachedFromWindow() {
    117         super.onDetachedFromWindow();
    118         if (mController != null) {
    119             mController.unregisterStateListener(mStateListener);
    120         }
    121     }
    122 
    123     private void initControllerView() {
    124         mPauseButton = (ImageButton) findViewById(R.id.pause);
    125         if (mPauseButton != null) {
    126             mPauseButton.requestFocus();
    127             mPauseButton.setOnClickListener(mPauseListener);
    128         }
    129 
    130         mFfwdButton = (ImageButton) findViewById(R.id.ffwd);
    131         if (mFfwdButton != null) {
    132             mFfwdButton.setOnClickListener(mFfwdListener);
    133             mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
    134         }
    135 
    136         mRewButton = (ImageButton) findViewById(R.id.rew);
    137         if (mRewButton != null) {
    138             mRewButton.setOnClickListener(mRewListener);
    139             mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
    140         }
    141 
    142         // By default these are hidden. They will be enabled when setPrevNextListeners() is called
    143         mNextButton = (ImageButton) findViewById(R.id.next);
    144         if (mNextButton != null && !mListenersSet) {
    145             mNextButton.setVisibility(View.GONE);
    146         }
    147         mPrevButton = (ImageButton) findViewById(R.id.prev);
    148         if (mPrevButton != null && !mListenersSet) {
    149             mPrevButton.setVisibility(View.GONE);
    150         }
    151 
    152         mProgress = (ProgressBar) findViewById(R.id.mediacontroller_progress);
    153         if (mProgress != null) {
    154             if (mProgress instanceof SeekBar) {
    155                 SeekBar seeker = (SeekBar) mProgress;
    156                 seeker.setOnSeekBarChangeListener(mSeekListener);
    157             }
    158             mProgress.setMax(1000);
    159         }
    160 
    161         mEndTime = (TextView) findViewById(R.id.time);
    162         mCurrentTime = (TextView) findViewById(R.id.time_current);
    163         mFormatBuilder = new StringBuilder();
    164         mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
    165 
    166         installPrevNextListeners();
    167     }
    168 
    169     /**
    170      * Disable pause or seek buttons if the stream cannot be paused or seeked.
    171      * This requires the control interface to be a MediaPlayerControlExt
    172      */
    173     void updateButtons() {
    174         int flags = mController.getTransportControlFlags();
    175         boolean enabled = isEnabled();
    176         if (mPauseButton != null) {
    177             mPauseButton.setEnabled(enabled && (flags&TransportMediator.FLAG_KEY_MEDIA_PAUSE) != 0);
    178         }
    179         if (mRewButton != null) {
    180             mRewButton.setEnabled(enabled && (flags&TransportMediator.FLAG_KEY_MEDIA_REWIND) != 0);
    181         }
    182         if (mFfwdButton != null) {
    183             mFfwdButton.setEnabled(enabled &&
    184                     (flags&TransportMediator.FLAG_KEY_MEDIA_FAST_FORWARD) != 0);
    185         }
    186         if (mPrevButton != null) {
    187             mShowPrev = (flags&TransportMediator.FLAG_KEY_MEDIA_PREVIOUS) != 0
    188                     || mPrevListener != null;
    189             mPrevButton.setEnabled(enabled && mShowPrev);
    190         }
    191         if (mNextButton != null) {
    192             mShowNext = (flags&TransportMediator.FLAG_KEY_MEDIA_NEXT) != 0
    193                     || mNextListener != null;
    194             mNextButton.setEnabled(enabled && mShowNext);
    195         }
    196     }
    197 
    198     public void refresh() {
    199         updateProgress();
    200         updateButtons();
    201         updatePausePlay();
    202     }
    203 
    204     private String stringForTime(int timeMs) {
    205         int totalSeconds = timeMs / 1000;
    206 
    207         int seconds = totalSeconds % 60;
    208         int minutes = (totalSeconds / 60) % 60;
    209         int hours   = totalSeconds / 3600;
    210 
    211         mFormatBuilder.setLength(0);
    212         if (hours > 0) {
    213             return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
    214         } else {
    215             return mFormatter.format("%02d:%02d", minutes, seconds).toString();
    216         }
    217     }
    218 
    219     public long updateProgress() {
    220         if (mController == null || mDragging) {
    221             return 0;
    222         }
    223         long position = mController.getCurrentPosition();
    224         long duration = mController.getDuration();
    225         if (mProgress != null) {
    226             if (duration > 0) {
    227                 // use long to avoid overflow
    228                 long pos = 1000L * position / duration;
    229                 mProgress.setProgress( (int) pos);
    230             }
    231             int percent = mController.getBufferPercentage();
    232             mProgress.setSecondaryProgress(percent * 10);
    233         }
    234 
    235         if (mEndTime != null)
    236             mEndTime.setText(stringForTime((int)duration));
    237         if (mCurrentTime != null)
    238             mCurrentTime.setText(stringForTime((int)position));
    239 
    240         return position;
    241     }
    242 
    243     private View.OnClickListener mPauseListener = new View.OnClickListener() {
    244         public void onClick(View v) {
    245             doPauseResume();
    246         }
    247     };
    248 
    249     private void updatePausePlay() {
    250         if (mPauseButton == null)
    251             return;
    252 
    253         if (mController.isPlaying()) {
    254             mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
    255         } else {
    256             mPauseButton.setImageResource(android.R.drawable.ic_media_play);
    257         }
    258     }
    259 
    260     private void doPauseResume() {
    261         if (mController.isPlaying()) {
    262             mController.pausePlaying();
    263         } else {
    264             mController.startPlaying();
    265         }
    266         updatePausePlay();
    267     }
    268 
    269     // There are two scenarios that can trigger the seekbar listener to trigger:
    270     //
    271     // The first is the user using the touchpad to adjust the posititon of the
    272     // seekbar's thumb. In this case onStartTrackingTouch is called followed by
    273     // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
    274     // We're setting the field "mDragging" to true for the duration of the dragging
    275     // session to avoid jumps in the position in case of ongoing playback.
    276     //
    277     // The second scenario involves the user operating the scroll ball, in this
    278     // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
    279     // we will simply apply the updated position without suspending regular updates.
    280     private SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
    281         public void onStartTrackingTouch(SeekBar bar) {
    282             mDragging = true;
    283         }
    284 
    285         public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
    286             if (!fromuser) {
    287                 // We're not interested in programmatically generated changes to
    288                 // the progress bar's position.
    289                 return;
    290             }
    291 
    292             long duration = mController.getDuration();
    293             long newposition = (duration * progress) / 1000L;
    294             mController.seekTo((int) newposition);
    295             if (mCurrentTime != null)
    296                 mCurrentTime.setText(stringForTime( (int) newposition));
    297         }
    298 
    299         public void onStopTrackingTouch(SeekBar bar) {
    300             mDragging = false;
    301             updateProgress();
    302             updatePausePlay();
    303         }
    304     };
    305 
    306     @Override
    307     public void setEnabled(boolean enabled) {
    308         super.setEnabled(enabled);
    309         updateButtons();
    310     }
    311 
    312     @Override
    313     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    314         super.onInitializeAccessibilityEvent(event);
    315         event.setClassName(MediaController.class.getName());
    316     }
    317 
    318     @Override
    319     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    320         super.onInitializeAccessibilityNodeInfo(info);
    321         info.setClassName(MediaController.class.getName());
    322     }
    323 
    324     private View.OnClickListener mRewListener = new View.OnClickListener() {
    325         public void onClick(View v) {
    326             long pos = mController.getCurrentPosition();
    327             pos -= 5000; // milliseconds
    328             mController.seekTo(pos);
    329             updateProgress();
    330         }
    331     };
    332 
    333     private View.OnClickListener mFfwdListener = new View.OnClickListener() {
    334         public void onClick(View v) {
    335             long pos = mController.getCurrentPosition();
    336             pos += 15000; // milliseconds
    337             mController.seekTo(pos);
    338             updateProgress();
    339         }
    340     };
    341 
    342     private void installPrevNextListeners() {
    343         if (mNextButton != null) {
    344             mNextButton.setOnClickListener(mNextListener);
    345             mNextButton.setEnabled(mShowNext);
    346         }
    347 
    348         if (mPrevButton != null) {
    349             mPrevButton.setOnClickListener(mPrevListener);
    350             mPrevButton.setEnabled(mShowPrev);
    351         }
    352     }
    353 
    354     public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {
    355         mNextListener = next;
    356         mPrevListener = prev;
    357         mListenersSet = true;
    358 
    359         installPrevNextListeners();
    360 
    361         if (mNextButton != null) {
    362             mNextButton.setVisibility(View.VISIBLE);
    363             mShowNext = true;
    364         }
    365         if (mPrevButton != null) {
    366             mPrevButton.setVisibility(View.VISIBLE);
    367             mShowPrev = true;
    368         }
    369     }
    370 }
    371