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