Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Streaming the Droid

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Streaming the Droid

Avatar for David González

David González

May 08, 2014
Tweet

More Decks by David González

Other Decks in Programming

Transcript

  1. 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)
  2. TextureView behaves as a regular View. This key difference allows

    a TextureView to be moved, transformed, animated, etc
  3. 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.<p> * <p/> * <em>Note: VideoView does not retain its full state when going into the * background.</em> 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}.<p> * 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 {
  4. 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;
  5. 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); } ... } };
  6. 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();
  7. 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; } };
  8. 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"; }
  9. 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"; }
  10. 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; } }
  11. Custom Controller /** * Controller to manage syncing the ui

    models with the UI Controls and MediaPlayer. * <p/> * Note that the ui models have a narrow scope (i.e. chapter list, piece navigation), * their interaction is orchestrated by this controller.ø * <p/> * 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 {
  12. 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()); } }
  13. 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(); } } }); }
  14. LESSONS LEARNED » Test on every possible device » Encrypted

    Video Streams are always a problem » Delay between Video / Audio » minSDK 14