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
}