blob: 86f51001dea98c871383082049ec7f1e6d3b8669 [file] [log] [blame]
Alexander Lucas97842ff2014-03-07 14:56:55 -08001/*
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.mediarouter.player;
18
19import android.app.Activity;
20import android.app.Presentation;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.media.MediaPlayer;
24import android.os.Build;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.SystemClock;
28import android.support.v7.media.MediaItemStatus;
29import android.support.v7.media.MediaRouter.RouteInfo;
30import android.util.Log;
31import android.view.Display;
32import android.view.Gravity;
33import android.view.Surface;
34import android.view.SurfaceHolder;
35import android.view.SurfaceView;
36import android.view.View;
37import android.view.ViewGroup;
38import android.view.WindowManager;
39import android.widget.FrameLayout;
40
41import com.example.android.mediarouter.R;
42
43import java.io.IOException;
44
45/**
46 * Handles playback of a single media item using MediaPlayer.
47 */
48public 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}