MediaRouter / src / com.example.android.mediarouter / player /

LocalPlayer.java

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