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