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