LocalPlayer.java revision cf61a6ed2bfa6141b832fdc40a9fbfb70af91416
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.graphics.Bitmap;
25import android.media.MediaPlayer;
26import android.net.Uri;
27import android.os.Build;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.SystemClock;
31import android.support.v7.media.MediaRouter.RouteInfo;
32import android.support.v7.media.MediaItemStatus;
33import android.util.Log;
34import android.view.Display;
35import android.view.Gravity;
36import android.view.Surface;
37import android.view.SurfaceHolder;
38import android.view.SurfaceView;
39import android.view.View;
40import android.view.ViewGroup;
41import android.view.WindowManager;
42import android.widget.FrameLayout;
43
44import com.example.android.supportv7.R;
45
46import java.io.IOException;
47
48/**
49 * Handles playback of a single media item using MediaPlayer.
50 */
51public abstract class LocalPlayer extends Player implements
52        MediaPlayer.OnPreparedListener,
53        MediaPlayer.OnCompletionListener,
54        MediaPlayer.OnErrorListener,
55        MediaPlayer.OnSeekCompleteListener {
56    private static final String TAG = "LocalPlayer";
57    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
58
59    private static final int STATE_IDLE = 0;
60    private static final int STATE_PLAY_PENDING = 1;
61    private static final int STATE_READY = 2;
62    private static final int STATE_PLAYING = 3;
63    private static final int STATE_PAUSED = 4;
64
65    private final Context mContext;
66    private final Handler mHandler = new Handler();
67    private MediaPlayer mMediaPlayer;
68    private int mState = STATE_IDLE;
69    private int mSeekToPos;
70    private int mVideoWidth;
71    private int mVideoHeight;
72    private Surface mSurface;
73    private SurfaceHolder mSurfaceHolder;
74
75    public LocalPlayer(Context context) {
76        mContext = context;
77
78        // reset media player
79        reset();
80    }
81
82    @Override
83    public boolean isRemotePlayback() {
84        return false;
85    }
86
87    @Override
88    public boolean isQueuingSupported() {
89        return false;
90    }
91
92    @Override
93    public void connect(RouteInfo route) {
94        if (DEBUG) {
95            Log.d(TAG, "connecting to: " + route);
96        }
97    }
98
99    @Override
100    public void release() {
101        if (DEBUG) {
102            Log.d(TAG, "releasing");
103        }
104        // release media player
105        if (mMediaPlayer != null) {
106            mMediaPlayer.stop();
107            mMediaPlayer.release();
108            mMediaPlayer = null;
109        }
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