Home | History | Annotate | Download | only in player
      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.mediarouter.player;
     18 
     19 import android.app.Activity;
     20 import android.app.Presentation;
     21 import android.content.Context;
     22 import android.content.DialogInterface;
     23 import android.media.MediaPlayer;
     24 import android.os.Build;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.SystemClock;
     28 import android.support.v7.media.MediaItemStatus;
     29 import android.support.v7.media.MediaRouter.RouteInfo;
     30 import android.util.Log;
     31 import android.view.Display;
     32 import android.view.Gravity;
     33 import android.view.Surface;
     34 import android.view.SurfaceHolder;
     35 import android.view.SurfaceView;
     36 import android.view.View;
     37 import android.view.ViewGroup;
     38 import android.view.WindowManager;
     39 import android.widget.FrameLayout;
     40 
     41 import com.example.android.mediarouter.R;
     42 
     43 import java.io.IOException;
     44 
     45 /**
     46  * Handles playback of a single media item using MediaPlayer.
     47  */
     48 public abstract class LocalPlayer extends Player implements
     49         MediaPlayer.OnPreparedListener,
     50         MediaPlayer.OnCompletionListener,
     51         MediaPlayer.OnErrorListener,
     52         MediaPlayer.OnSeekCompleteListener {
     53     private static final String TAG = "LocalPlayer";
     54     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     55 
     56     private static final int STATE_IDLE = 0;
     57     private static final int STATE_PLAY_PENDING = 1;
     58     private static final int STATE_READY = 2;
     59     private static final int STATE_PLAYING = 3;
     60     private static final int STATE_PAUSED = 4;
     61 
     62     private final Context mContext;
     63     private final Handler mHandler = new Handler();
     64     private MediaPlayer mMediaPlayer;
     65     private int mState = STATE_IDLE;
     66     private int mSeekToPos;
     67     private int mVideoWidth;
     68     private int mVideoHeight;
     69     private Surface mSurface;
     70     private SurfaceHolder mSurfaceHolder;
     71 
     72     public LocalPlayer(Context context) {
     73         mContext = context;
     74 
     75         // reset media player
     76         reset();
     77     }
     78 
     79     @Override
     80     public boolean isRemotePlayback() {
     81         return false;
     82     }
     83 
     84     @Override
     85     public boolean isQueuingSupported() {
     86         return false;
     87     }
     88 
     89     @Override
     90     public void connect(RouteInfo route) {
     91         if (DEBUG) {
     92             Log.d(TAG, "connecting to: " + route);
     93         }
     94     }
     95 
     96     @Override
     97     public void release() {
     98         if (DEBUG) {
     99             Log.d(TAG, "releasing");
    100         }
    101         // release media player
    102         if (mMediaPlayer != null) {
    103             mMediaPlayer.stop();
    104             mMediaPlayer.release();
    105             mMediaPlayer = null;
    106         }
    107     }
    108 
    109     // Player
    110     @Override
    111     public void play(final PlaylistItem item) {
    112         if (DEBUG) {
    113             Log.d(TAG, "play: item=" + item);
    114         }
    115         reset();
    116         mSeekToPos = (int)item.getPosition();
    117         try {
    118             mMediaPlayer.setDataSource(mContext, item.getUri());
    119             mMediaPlayer.prepareAsync();
    120         } catch (IllegalStateException e) {
    121             Log.e(TAG, "MediaPlayer throws IllegalStateException, uri=" + item.getUri());
    122         } catch (IOException e) {
    123             Log.e(TAG, "MediaPlayer throws IOException, uri=" + item.getUri());
    124         } catch (IllegalArgumentException e) {
    125             Log.e(TAG, "MediaPlayer throws IllegalArgumentException, uri=" + item.getUri());
    126         } catch (SecurityException e) {
    127             Log.e(TAG, "MediaPlayer throws SecurityException, uri=" + item.getUri());
    128         }
    129         if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
    130             resume();
    131         } else {
    132             pause();
    133         }
    134     }
    135 
    136     @Override
    137     public void seek(final PlaylistItem item) {
    138         if (DEBUG) {
    139             Log.d(TAG, "seek: item=" + item);
    140         }
    141         int pos = (int)item.getPosition();
    142         if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
    143             mMediaPlayer.seekTo(pos);
    144             mSeekToPos = pos;
    145         } else if (mState == STATE_IDLE || mState == STATE_PLAY_PENDING) {
    146             // Seek before onPrepared() arrives,
    147             // need to performed delayed seek in onPrepared()
    148             mSeekToPos = pos;
    149         }
    150     }
    151 
    152     @Override
    153     public void getStatus(final PlaylistItem item, final boolean update) {
    154         if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
    155             // use mSeekToPos if we're currently seeking (mSeekToPos is reset
    156             // when seeking is completed)
    157             item.setDuration(mMediaPlayer.getDuration());
    158             item.setPosition(mSeekToPos > 0 ?
    159                     mSeekToPos : mMediaPlayer.getCurrentPosition());
    160             item.setTimestamp(SystemClock.elapsedRealtime());
    161         }
    162         if (update && mCallback != null) {
    163             mCallback.onPlaylistReady();
    164         }
    165     }
    166 
    167     @Override
    168     public void pause() {
    169         if (DEBUG) {
    170             Log.d(TAG, "pause");
    171         }
    172         if (mState == STATE_PLAYING) {
    173             mMediaPlayer.pause();
    174             mState = STATE_PAUSED;
    175         }
    176     }
    177 
    178     @Override
    179     public void resume() {
    180         if (DEBUG) {
    181             Log.d(TAG, "resume");
    182         }
    183         if (mState == STATE_READY || mState == STATE_PAUSED) {
    184             mMediaPlayer.start();
    185             mState = STATE_PLAYING;
    186         } else if (mState == STATE_IDLE){
    187             mState = STATE_PLAY_PENDING;
    188         }
    189     }
    190 
    191     @Override
    192     public void stop() {
    193         if (DEBUG) {
    194             Log.d(TAG, "stop");
    195         }
    196         if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
    197             mMediaPlayer.stop();
    198             mState = STATE_IDLE;
    199         }
    200     }
    201 
    202     @Override
    203     public void enqueue(final PlaylistItem item) {
    204         throw new UnsupportedOperationException("LocalPlayer doesn't support enqueue!");
    205     }
    206 
    207     @Override
    208     public PlaylistItem remove(String iid) {
    209         throw new UnsupportedOperationException("LocalPlayer doesn't support remove!");
    210     }
    211 
    212     //MediaPlayer Listeners
    213     @Override
    214     public void onPrepared(MediaPlayer mp) {
    215         if (DEBUG) {
    216             Log.d(TAG, "onPrepared");
    217         }
    218         mHandler.post(new Runnable() {
    219             @Override
    220             public void run() {
    221                 if (mState == STATE_IDLE) {
    222                     mState = STATE_READY;
    223                     updateVideoRect();
    224                 } else if (mState == STATE_PLAY_PENDING) {
    225                     mState = STATE_PLAYING;
    226                     updateVideoRect();
    227                     if (mSeekToPos > 0) {
    228                         if (DEBUG) {
    229                             Log.d(TAG, "seek to initial pos: " + mSeekToPos);
    230                         }
    231                         mMediaPlayer.seekTo(mSeekToPos);
    232                     }
    233                     mMediaPlayer.start();
    234                 }
    235                 if (mCallback != null) {
    236                     mCallback.onPlaylistChanged();
    237                 }
    238             }
    239         });
    240     }
    241 
    242     @Override
    243     public void onCompletion(MediaPlayer mp) {
    244         if (DEBUG) {
    245             Log.d(TAG, "onCompletion");
    246         }
    247         mHandler.post(new Runnable() {
    248             @Override
    249             public void run() {
    250                 if (mCallback != null) {
    251                     mCallback.onCompletion();
    252                 }
    253             }
    254         });
    255     }
    256 
    257     @Override
    258     public boolean onError(MediaPlayer mp, int what, int extra) {
    259         if (DEBUG) {
    260             Log.d(TAG, "onError");
    261         }
    262         mHandler.post(new Runnable() {
    263             @Override
    264             public void run() {
    265                 if (mCallback != null) {
    266                     mCallback.onError();
    267                 }
    268             }
    269         });
    270         // return true so that onCompletion is not called
    271         return true;
    272     }
    273 
    274     @Override
    275     public void onSeekComplete(MediaPlayer mp) {
    276         if (DEBUG) {
    277             Log.d(TAG, "onSeekComplete");
    278         }
    279         mHandler.post(new Runnable() {
    280             @Override
    281             public void run() {
    282                 mSeekToPos = 0;
    283                 if (mCallback != null) {
    284                     mCallback.onPlaylistChanged();
    285                 }
    286             }
    287         });
    288     }
    289 
    290     protected Context getContext() { return mContext; }
    291     protected MediaPlayer getMediaPlayer() { return mMediaPlayer; }
    292     protected int getVideoWidth() { return mVideoWidth; }
    293     protected int getVideoHeight() { return mVideoHeight; }
    294     protected void setSurface(Surface surface) {
    295         mSurface = surface;
    296         mSurfaceHolder = null;
    297         updateSurface();
    298     }
    299 
    300     protected void setSurface(SurfaceHolder surfaceHolder) {
    301         mSurface = null;
    302         mSurfaceHolder = surfaceHolder;
    303         updateSurface();
    304     }
    305 
    306     protected void removeSurface(SurfaceHolder surfaceHolder) {
    307         if (surfaceHolder == mSurfaceHolder) {
    308             setSurface((SurfaceHolder)null);
    309         }
    310     }
    311 
    312     protected void updateSurface() {
    313         if (mMediaPlayer == null) {
    314             // just return if media player is already gone
    315             return;
    316         }
    317         if (mSurface != null) {
    318             // The setSurface API does not exist until V14+.
    319             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    320                 ICSMediaPlayer.setSurface(mMediaPlayer, mSurface);
    321             } else {
    322                 throw new UnsupportedOperationException("MediaPlayer does not support "
    323                         + "setSurface() on this version of the platform.");
    324             }
    325         } else if (mSurfaceHolder != null) {
    326             mMediaPlayer.setDisplay(mSurfaceHolder);
    327         } else {
    328             mMediaPlayer.setDisplay(null);
    329         }
    330     }
    331 
    332     protected abstract void updateSize();
    333 
    334     private void reset() {
    335         if (mMediaPlayer != null) {
    336             mMediaPlayer.stop();
    337             mMediaPlayer.release();
    338             mMediaPlayer = null;
    339         }
    340         mMediaPlayer = new MediaPlayer();
    341         mMediaPlayer.setOnPreparedListener(this);
    342         mMediaPlayer.setOnCompletionListener(this);
    343         mMediaPlayer.setOnErrorListener(this);
    344         mMediaPlayer.setOnSeekCompleteListener(this);
    345         updateSurface();
    346         mState = STATE_IDLE;
    347         mSeekToPos = 0;
    348     }
    349 
    350     private void updateVideoRect() {
    351         if (mState != STATE_IDLE && mState != STATE_PLAY_PENDING) {
    352             int width = mMediaPlayer.getVideoWidth();
    353             int height = mMediaPlayer.getVideoHeight();
    354             if (width > 0 && height > 0) {
    355                 mVideoWidth = width;
    356                 mVideoHeight = height;
    357                 updateSize();
    358             } else {
    359                 Log.e(TAG, "video rect is 0x0!");
    360                 mVideoWidth = mVideoHeight = 0;
    361             }
    362         }
    363     }
    364 
    365     private static final class ICSMediaPlayer {
    366         public static final void setSurface(MediaPlayer player, Surface surface) {
    367             player.setSurface(surface);
    368         }
    369     }
    370 
    371     /**
    372      * Handles playback of a single media item using MediaPlayer in SurfaceView
    373      */
    374     public static class SurfaceViewPlayer extends LocalPlayer implements
    375             SurfaceHolder.Callback {
    376         private static final String TAG = "SurfaceViewPlayer";
    377         private RouteInfo mRoute;
    378         private final SurfaceView mSurfaceView;
    379         private final FrameLayout mLayout;
    380         private DemoPresentation mPresentation;
    381 
    382         public SurfaceViewPlayer(Context context) {
    383             super(context);
    384 
    385             mLayout = (FrameLayout)((Activity)context).findViewById(R.id.player);
    386             mSurfaceView = (SurfaceView)((Activity)context).findViewById(R.id.surface_view);
    387 
    388             // add surface holder callback
    389             SurfaceHolder holder = mSurfaceView.getHolder();
    390             holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    391             holder.addCallback(this);
    392         }
    393 
    394         @Override
    395         public void connect(RouteInfo route) {
    396             super.connect(route);
    397             mRoute = route;
    398         }
    399 
    400         @Override
    401         public void release() {
    402             super.release();
    403 
    404             // dismiss presentation display
    405             if (mPresentation != null) {
    406                 Log.i(TAG, "Dismissing presentation because the activity is no longer visible.");
    407                 mPresentation.dismiss();
    408                 mPresentation = null;
    409             }
    410 
    411             // remove surface holder callback
    412             SurfaceHolder holder = mSurfaceView.getHolder();
    413             holder.removeCallback(this);
    414 
    415             // hide the surface view when SurfaceViewPlayer is destroyed
    416             mSurfaceView.setVisibility(View.GONE);
    417             mLayout.setVisibility(View.GONE);
    418         }
    419 
    420         @Override
    421         public void updatePresentation() {
    422             // Get the current route and its presentation display.
    423             Display presentationDisplay = mRoute != null ? mRoute.getPresentationDisplay() : null;
    424 
    425             // Dismiss the current presentation if the display has changed.
    426             if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) {
    427                 Log.i(TAG, "Dismissing presentation because the current route no longer "
    428                         + "has a presentation display.");
    429                 mPresentation.dismiss();
    430                 mPresentation = null;
    431             }
    432 
    433             // Show a new presentation if needed.
    434             if (mPresentation == null && presentationDisplay != null) {
    435                 Log.i(TAG, "Showing presentation on display: " + presentationDisplay);
    436                 mPresentation = new DemoPresentation(getContext(), presentationDisplay);
    437                 mPresentation.setOnDismissListener(mOnDismissListener);
    438                 try {
    439                     mPresentation.show();
    440                 } catch (WindowManager.InvalidDisplayException ex) {
    441                     Log.w(TAG, "Couldn't show presentation!  Display was removed in "
    442                               + "the meantime.", ex);
    443                     mPresentation = null;
    444                 }
    445             }
    446 
    447             updateContents();
    448         }
    449 
    450         // SurfaceHolder.Callback
    451         @Override
    452         public void surfaceChanged(SurfaceHolder holder, int format,
    453                 int width, int height) {
    454             if (DEBUG) {
    455                 Log.d(TAG, "surfaceChanged: " + width + "x" + height);
    456             }
    457             setSurface(holder);
    458         }
    459 
    460         @Override
    461         public void surfaceCreated(SurfaceHolder holder) {
    462             if (DEBUG) {
    463                 Log.d(TAG, "surfaceCreated");
    464             }
    465             setSurface(holder);
    466             updateSize();
    467         }
    468 
    469         @Override
    470         public void surfaceDestroyed(SurfaceHolder holder) {
    471             if (DEBUG) {
    472                 Log.d(TAG, "surfaceDestroyed");
    473             }
    474             removeSurface(holder);
    475         }
    476 
    477         @Override
    478         protected void updateSize() {
    479             int width = getVideoWidth();
    480             int height = getVideoHeight();
    481             if (width > 0 && height > 0) {
    482                 if (mPresentation == null) {
    483                     int surfaceWidth = mLayout.getWidth();
    484                     int surfaceHeight = mLayout.getHeight();
    485 
    486                     // Calculate the new size of mSurfaceView, so that video is centered
    487                     // inside the framelayout with proper letterboxing/pillarboxing
    488                     ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams();
    489                     if (surfaceWidth * height < surfaceHeight * width) {
    490                         // Black bars on top&bottom, mSurfaceView has full layout width,
    491                         // while height is derived from video's aspect ratio
    492                         lp.width = surfaceWidth;
    493                         lp.height = surfaceWidth * height / width;
    494                     } else {
    495                         // Black bars on left&right, mSurfaceView has full layout height,
    496                         // while width is derived from video's aspect ratio
    497                         lp.width = surfaceHeight * width / height;
    498                         lp.height = surfaceHeight;
    499                     }
    500                     Log.i(TAG, "video rect is " + lp.width + "x" + lp.height);
    501                     mSurfaceView.setLayoutParams(lp);
    502                 } else {
    503                     mPresentation.updateSize(width, height);
    504                 }
    505             }
    506         }
    507 
    508         private void updateContents() {
    509             // Show either the content in the main activity or the content in the presentation
    510             if (mPresentation != null) {
    511                 mLayout.setVisibility(View.GONE);
    512                 mSurfaceView.setVisibility(View.GONE);
    513             } else {
    514                 mLayout.setVisibility(View.VISIBLE);
    515                 mSurfaceView.setVisibility(View.VISIBLE);
    516             }
    517         }
    518 
    519         // Listens for when presentations are dismissed.
    520         private final DialogInterface.OnDismissListener mOnDismissListener =
    521                 new DialogInterface.OnDismissListener() {
    522             @Override
    523             public void onDismiss(DialogInterface dialog) {
    524                 if (dialog == mPresentation) {
    525                     Log.i(TAG, "Presentation dismissed.");
    526                     mPresentation = null;
    527                     updateContents();
    528                 }
    529             }
    530         };
    531 
    532         // Presentation
    533         private final class DemoPresentation extends Presentation {
    534             private SurfaceView mPresentationSurfaceView;
    535 
    536             public DemoPresentation(Context context, Display display) {
    537                 super(context, display);
    538             }
    539 
    540             @Override
    541             protected void onCreate(Bundle savedInstanceState) {
    542                 // Be sure to call the super class.
    543                 super.onCreate(savedInstanceState);
    544 
    545                 // Inflate the layout.
    546                 setContentView(R.layout.sample_media_router_presentation);
    547 
    548                 // Set up the surface view.
    549                 mPresentationSurfaceView = (SurfaceView)findViewById(R.id.surface_view);
    550                 SurfaceHolder holder = mPresentationSurfaceView.getHolder();
    551                 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    552                 holder.addCallback(SurfaceViewPlayer.this);
    553                 Log.i(TAG, "Presentation created");
    554             }
    555 
    556             public void updateSize(int width, int height) {
    557                 int surfaceHeight = getWindow().getDecorView().getHeight();
    558                 int surfaceWidth = getWindow().getDecorView().getWidth();
    559                 ViewGroup.LayoutParams lp = mPresentationSurfaceView.getLayoutParams();
    560                 if (surfaceWidth * height < surfaceHeight * width) {
    561                     lp.width = surfaceWidth;
    562                     lp.height = surfaceWidth * height / width;
    563                 } else {
    564                     lp.width = surfaceHeight * width / height;
    565                     lp.height = surfaceHeight;
    566                 }
    567                 Log.i(TAG, "Presentation video rect is " + lp.width + "x" + lp.height);
    568                 mPresentationSurfaceView.setLayoutParams(lp);
    569             }
    570         }
    571     }
    572 
    573     /**
    574      * Handles playback of a single media item using MediaPlayer in
    575      * OverlayDisplayWindow.
    576      */
    577     public static class OverlayPlayer extends LocalPlayer implements
    578             OverlayDisplayWindow.OverlayWindowListener {
    579         private static final String TAG = "OverlayPlayer";
    580         private final OverlayDisplayWindow mOverlay;
    581 
    582         public OverlayPlayer(Context context) {
    583             super(context);
    584 
    585             mOverlay = OverlayDisplayWindow.create(getContext(),
    586                     getContext().getResources().getString(
    587                             R.string.sample_media_route_provider_remote),
    588                     1024, 768, Gravity.CENTER);
    589 
    590             mOverlay.setOverlayWindowListener(this);
    591         }
    592 
    593         @Override
    594         public void connect(RouteInfo route) {
    595             super.connect(route);
    596             mOverlay.show();
    597         }
    598 
    599         @Override
    600         public void release() {
    601             super.release();
    602             mOverlay.dismiss();
    603         }
    604 
    605         @Override
    606         protected void updateSize() {
    607             int width = getVideoWidth();
    608             int height = getVideoHeight();
    609             if (width > 0 && height > 0) {
    610                 mOverlay.updateAspectRatio(width, height);
    611             }
    612         }
    613 
    614         // OverlayDisplayWindow.OverlayWindowListener
    615         @Override
    616         public void onWindowCreated(Surface surface) {
    617             setSurface(surface);
    618         }
    619 
    620         @Override
    621         public void onWindowCreated(SurfaceHolder surfaceHolder) {
    622             setSurface(surfaceHolder);
    623         }
    624 
    625         @Override
    626         public void onWindowDestroyed() {
    627             setSurface((SurfaceHolder)null);
    628         }
    629     }
    630 }
    631