Slide 1

Slide 1 text

Streaming to the Droid David Gonzalez Android Software Craftsman

Slide 2

Slide 2 text

Who is this guy?

Slide 3

Slide 3 text

What is he going to talk about?

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Streaming protocols » Apple’s HTTP Live Streaming (HLS) » Adobe’s HTTP Dynamic Streaming (HDS) » Dynamic Adaptive Streaming over HTTP (aka MPEG- DASH) » Real Time Streaming Protocol (RTSP)

Slide 6

Slide 6 text

HLS Support

Slide 7

Slide 7 text

2.3 Gingerbread No native support

Slide 8

Slide 8 text

3.0 Honeycomb HoneyWHAT?

Slide 9

Slide 9 text

Solutions » Commercial: Adobe Prime Time, etc » Native: Custom native solution

Slide 10

Slide 10 text

Let's talk about Native TextureView vs SurfaceView

Slide 11

Slide 11 text

SurfaceView Creates a separate window Current VideoView is a SurfaceView

Slide 12

Slide 12 text

TextureView behaves as a regular View. This key difference allows a TextureView to be moved, transformed, animated, etc

Slide 13

Slide 13 text

Our approach /** * Displays a video file. The VideoView class * can load images from various sources (such as resources or content * providers), takes care of computing its measurement from the video so that * it can be used in any layout manager, and provides various display options * such as scaling and tinting.

*

* Note: VideoView does not retain its full state when going into the * background. In particular, it does not restore the current play state, * play position, selected tracks added via * {@link android.app.Activity#onSaveInstanceState} and * {@link android.app.Activity#onRestoreInstanceState}.

* Also note that the audio session id (from {@link #getAudioSessionId}) may * change from its previously returned value when the VideoView is restored. */ public class TextureVideoView extends android.view.TextureView implements Player {

Slide 14

Slide 14 text

Preparing for playing mMediaPlayer.setDataSource(getContext(), mUri, mHeaders); mMediaPlayer.setSurface(new Surface(mSurfaceTexture)); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setScreenOnWhilePlaying(true); mMediaPlayer.prepareAsync(); // we don't set the target state here either, but preserve the target state that was there before. mCurrentState = STATE_PREPARING;

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

OnPrepared Listener mMediaPlayer.setOnPreparedListener(mPreparedListener); private MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(final MediaPlayer mp) { mCurrentState = STATE_PREPARED; mCanPause = true; mCanSeekBack = true; mCanSeekForward = true; if (mOnPreparedListener != null) { mOnPreparedListener.onPrepared(mMediaPlayer); } if (mConcertPlayerController != null) { mConcertPlayerController.setEnabled(true); } if (seekToPosition != 0) { seekTo(seekToPosition); } ... } };

Slide 17

Slide 17 text

OnPrepared Listener videoSizeCalculator.setVideoSize(mp.getVideoWidth(), mp.getVideoHeight()); if (videoSizeCalculator.hasASizeYet()) { // We didn't actually change the size (it was already at the size we need), // so we won't get a "surface changed" callback, // so start the video here instead of in the callback. if (mTargetState == STATE_PLAYING) { start(); showMediaController(); } else if (pausedAt(seekToPosition)) { showStickyMediaController(); } } else { // We don't know the video size yet, but should start anyway. // The video size might be reported to us later. if (mTargetState == STATE_PLAYING) { start();

Slide 18

Slide 18 text

OnErrorListener Listener mMediaPlayer.setOnErrorListener(mErrorListener); private OnErrorListener mErrorListener = new OnErrorListener() { @Override public boolean onError(final MediaPlayer mp, final int frameworkError, final int implError) { if (mCurrentState == STATE_ERROR) { return true; } mCurrentState = STATE_ERROR; mTargetState = STATE_ERROR; hideMediaController(); if (allowPlayStateToHandle(frameworkError)) { return true; } if (allowErrorListenerToHandle(frameworkError, implError)) { return true; } handleError(frameworkError); return true; } };

Slide 19

Slide 19 text

OnErrorListener Listener switch (what) { case -1004: errorMessage += "MEDIA_ERROR_IO"; case -1007: errorMessage += "MEDIA_ERROR_MALFORMED"; case 200: errorMessage += "MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK"; case 100: errorMessage += "MEDIA_ERROR_SERVER_DIED"; case 1: errorMessage += "MEDIA_ERROR_UNKNOWN"; case -1010: errorMessage += "MEDIA_ERROR_UNSUPPORTED"; default: errorMessage += "MEDIA_ERROR_UNKNOWN"; }

Slide 20

Slide 20 text

OnErrorListener Listener switch (extra) { case 800: errorMessage += "MEDIA_INFO_BAD_INTERLEAVING"; case 702: errorMessage += "MEDIA_INFO_BUFFERING_END"; case 701: errorMessage += "MEDIA_INFO_METADATA_UPDATE"; case 802: errorMessage += "MEDIA_INFO_METADATA_UPDATE"; case 801: errorMessage += "MEDIA_INFO_NOT_SEEKABLE"; case 1: errorMessage += "MEDIA_INFO_UNKNOWN"; case 3: errorMessage += "MEDIA_INFO_VIDEO_RENDERING_START"; case 700: errorMessage += "MEDIA_INFO_VIDEO_TRACK_LAGGING"; case -110: errorMessage += "MEDIA_ERROR_TIMED_OUT"; default: errorMessage += "MEDIA_INFO_UNKNOWN"; }

Slide 21

Slide 21 text

OnInfoListener Listener mMediaPlayer.setOnInfoListener(mInfoListener); private final OnInfoListener onInfoToPlayStateListener = new OnInfoListener() { @Override public boolean onInfo(final MediaPlayer mp, final int what, final int extra) { if (noPlayStateListener()) { return false; } if (MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START == what) { onPlayStateListener.onFirstVideoFrameRendered(); onPlayStateListener.onPlay(); } if (MediaPlayer.MEDIA_INFO_BUFFERING_START == what) { onPlayStateListener.onBuffer(); } if (MediaPlayer.MEDIA_INFO_BUFFERING_END == what) { onPlayStateListener.onPlay(); } return false; } }

Slide 22

Slide 22 text

Custom listeners mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Custom Controller /** * Controller to manage syncing the ui models with the UI Controls and MediaPlayer. *

* Note that the ui models have a narrow scope (i.e. chapter list, piece navigation), * their interaction is orchestrated by this controller.ø *

* It's actually a view currently, as is the android MediaController. * (which is a bit odd and should be subject to change.) */ public final class ConcertPlayerController extends FrameLayout implements VideoTouchRoot.OnTouchReceiver, TextureVideoView.VideoController, TextureVideoView.OnPlayStateListener {

Slide 26

Slide 26 text

Custom Controller @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_play_concert); ... bindViews(); } private void attachMediaController() { if (mMediaPlayer != null && mConcertPlayerController != null) { mConcertPlayerController.setMediaPlayer(this); View anchorView = this.getParent() instanceof View ? (View) this.getParent() : this; mConcertPlayerController.setAnchorView(anchorView); mConcertPlayerController.setEnabled(isInPlaybackState()); } }

Slide 27

Slide 27 text

System UI private void setSystemUiVisibility(final boolean visible) { // always use the full screen estate to avoid resizing flicker int newVis = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; if (!visible) { newVis |= View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; } final View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility(newVis); decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(final int visibility) { if ((visibility & View.SYSTEM_UI_FLAG_LOW_PROFILE) == 0) { //no low_profile flag means show controls concertPlayerController.show(); } } }); }

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Don't trust the device

Slide 32

Slide 32 text

LESSONS LEARNED » Test on every possible device » Encrypted Video Streams are always a problem » Delay between Video / Audio » minSDK 14

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

Further reading https://github.com/novoda/fenster https://github.com/google/grafika

Slide 35

Slide 35 text

THANKS