Android Open Source - controldlna Route Fragment






From Project

Back to project page controldlna.

License

The source code is released under:

Copyright (c) 2013, Felix Ableitner All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:...

If you think the Android project controldlna listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
Copyright (c) 2013, Felix Ableitner/*from   w  w  w .j  av  a 2  s  .  c  om*/
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
    names of its contributors may be used to endorse or promote products
    derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.github.nutomic.controldlna.gui;

import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.support.v7.app.MediaRouteDiscoveryFragment;
import android.support.v7.media.MediaControlIntent;
import android.support.v7.media.MediaItemStatus;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.Callback;
import android.support.v7.media.MediaRouter.ProviderInfo;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;

import com.github.nutomic.controldlna.R;
import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener;
import com.github.nutomic.controldlna.mediarouter.MediaRouterPlayService;
import com.github.nutomic.controldlna.mediarouter.MediaRouterPlayServiceBinder;
import com.github.nutomic.controldlna.utility.FileArrayAdapter;
import com.github.nutomic.controldlna.utility.RouteAdapter;

import org.teleal.cling.support.model.item.Item;

import java.util.List;

/**
 * Controls media playback by showing a list of routes, and after selecting one,
 * the current playlist and playback controls.
 *
 * @author Felix Ableitner
 *
 */
public class RouteFragment extends MediaRouteDiscoveryFragment implements
    OnBackPressedListener, OnItemClickListener, OnClickListener,
    OnSeekBarChangeListener, OnScrollListener {

  private ListView mListView;

  private View mControls;
  private SeekBar mProgressBar;
  private ImageButton mPlayPause;
  private ImageButton mShuffle;
  private ImageButton mRepeat;
  private TextView mCurrentTimeView;
  private TextView mTotalTimeView;
  private TextView mEmptyView;

  private View mCurrentTrackView;

  private boolean mPlaying;

  private RouteAdapter mRouteAdapter;

  private FileArrayAdapter mPlaylistAdapter;

  private RouteInfo mSelectedRoute;

  /**
   * Count of the number of taps on the previous button within the
   * doubletap interval.
   */
  private int mPreviousTapCount = 0;

  /**
   * If true, the item at this position will be played as soon as a route is selected.
   */
  private int mStartPlayingOnSelect = -1;

  private MediaRouterPlayService mMediaRouterPlayService;

  private ServiceConnection mPlayServiceConnection = new ServiceConnection() {

    public void onServiceConnected(ComponentName className, IBinder service) {
      MediaRouterPlayServiceBinder binder = (MediaRouterPlayServiceBinder) service;
      mMediaRouterPlayService = binder.getService();
      mMediaRouterPlayService.setRouterFragment(RouteFragment.this);
      mPlaylistAdapter.add(mMediaRouterPlayService.getPlaylist());
      applyColors();
      RouteInfo currentRoute = mMediaRouterPlayService.getCurrentRoute();
      if (currentRoute != null) {
        playlistMode(currentRoute);
      }
    }

    public void onServiceDisconnected(ComponentName className) {
      mMediaRouterPlayService = null;
    }
  };

  /**
   * Selects remote playback route category.
   */
  public RouteFragment() {
    MediaRouteSelector mSelector = new MediaRouteSelector.Builder()
        .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
        .build();
    setRouteSelector(mSelector);
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {

    return inflater.inflate(R.layout.route_fragment, null);
  };

  /**
   * Initializes views, connects to service, adds default route.
   */
  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    mRouteAdapter = new RouteAdapter(getActivity());
    mRouteAdapter.add(MediaRouter.getInstance(getActivity()).getRoutes());
    mRouteAdapter.remove(MediaRouter.getInstance(getActivity()).getDefaultRoute());
    mRouteAdapter.sort(RouteAdapter.COMPARATOR);
    mPlaylistAdapter = new FileArrayAdapter(getActivity());

    mListView = (ListView) getView().findViewById(R.id.listview);
    mListView.setAdapter(mRouteAdapter);
    mListView.setOnItemClickListener(this);
    mListView.setOnScrollListener(this);

    mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
    mListView.setEmptyView(mEmptyView);

    mControls = getView().findViewById(R.id.controls);
    mProgressBar = (SeekBar) getView().findViewById(R.id.progressBar);
    mProgressBar.setOnSeekBarChangeListener(this);

    mShuffle = (ImageButton) getView().findViewById(R.id.shuffle);
    mShuffle.setImageResource(R.drawable.ic_action_shuffle);
    mShuffle.setOnClickListener(this);

    ImageButton previous = (ImageButton) getView().findViewById(R.id.previous);
    previous.setImageResource(R.drawable.ic_action_previous);
    previous.setOnClickListener(this);

    ImageButton next = (ImageButton) getView().findViewById(R.id.next);
    next.setImageResource(R.drawable.ic_action_next);
    next.setOnClickListener(this);

    mRepeat = (ImageButton) getView().findViewById(R.id.repeat);
    mRepeat.setImageResource(R.drawable.ic_action_repeat);
    mRepeat.setOnClickListener(this);

    mPlayPause = (ImageButton) getView().findViewById(R.id.playpause);
    mPlayPause.setOnClickListener(this);
    mPlayPause.setImageResource(R.drawable.ic_action_play);

    mCurrentTimeView = (TextView) getView().findViewById(R.id.current_time);
    mTotalTimeView = (TextView) getView().findViewById(R.id.total_time);

    getActivity().getApplicationContext().startService(
        new Intent(getActivity(), MediaRouterPlayService.class));
    getActivity().getApplicationContext().bindService(
        new Intent(getActivity(), MediaRouterPlayService.class),
        mPlayServiceConnection, Context.BIND_AUTO_CREATE);

    if (savedInstanceState != null) {
      mListView.onRestoreInstanceState(savedInstanceState.getParcelable("list_state"));
    }
  }

  @Override
  public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    outState.putParcelable("list_state", mListView.onSaveInstanceState());
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    getActivity().getApplicationContext().unbindService(mPlayServiceConnection);
  }

  /**
   * Starts active route discovery (which is automatically stopped on
   * fragment stop by parent class).
   */
  @Override
  public int onPrepareCallbackFlags() {
    return  MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY |
        MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN;
  }

  @Override
  public Callback onCreateCallback() {
    return new MediaRouter.Callback() {
      @Override
      public void onRouteAdded(MediaRouter router, RouteInfo route) {
        for (int i = 0; i < mRouteAdapter.getCount(); i++) {
          if (mRouteAdapter.getItem(i).getId().equals(route.getId())) {
            mRouteAdapter.remove(mRouteAdapter.getItem(i));
            break;
          }
        }
        mRouteAdapter.add(route);
        mRouteAdapter.sort(RouteAdapter.COMPARATOR);

        RouteInfo current = mMediaRouterPlayService.getCurrentRoute();
        if (current != null && route.getId().equals(current.getId())) {
          playlistMode(current);
        }
      }

      @Override
      public void onRouteChanged(MediaRouter router, RouteInfo route) {
        mRouteAdapter.notifyDataSetChanged();
      }

      @Override
      public void onRouteRemoved(MediaRouter router, RouteInfo route) {
        mRouteAdapter.remove(route);
        if (route.equals(mSelectedRoute)) {
          mPlaying = false;
          onBackPressed();
        }
      }

      @Override
      public void onRouteSelected(MediaRouter router, RouteInfo route) {
      }

      @Override
      public void onRouteUnselected(MediaRouter router, RouteInfo route) {
      }

      @Override
      public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
      }

      @Override
      public void onRoutePresentationDisplayChanged(
          MediaRouter router, RouteInfo route) {
      }

      @Override
      public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
      }

      @Override
      public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
      }

      @Override
      public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
      }
    };

  }

  /**
   * Selects a route or starts playback (depending on current ListAdapter).
   */
  @Override
  public void onItemClick(AdapterView<?> a, View v, final int position, long id) {
    if (mListView.getAdapter() == mRouteAdapter) {
      playlistMode(mRouteAdapter.getItem(position));
    }
    else {
      mMediaRouterPlayService.play(position);
      changePlayPauseState(true);
    }
  }

  /**
   * Displays UPNP devices in the ListView.
   */
  private void deviceListMode() {
    mControls.setVisibility(View.GONE);
    mListView.setAdapter(mRouteAdapter);
    disableTrackHighlight();
    mSelectedRoute = null;
    mEmptyView.setText(R.string.route_list_empty);
  }

  /**
   * Displays playlist for route in the ListView.
   */
  private void playlistMode(RouteInfo route) {
    mSelectedRoute = route;
    mMediaRouterPlayService.selectRoute(mSelectedRoute);
    mListView.setAdapter(mPlaylistAdapter);
    mControls.setVisibility(View.VISIBLE);
    if (mStartPlayingOnSelect != -1) {
      mMediaRouterPlayService.play(mStartPlayingOnSelect);
      changePlayPauseState(true);
      mStartPlayingOnSelect = -1;
    }
    mEmptyView.setText(R.string.playlist_empty);
    mListView.post(new Runnable() {
      @Override
      public void run() {
        scrollToCurrent();
      }
    });
  }

  /**
   * Sets colored background on the item that is currently playing.
   */
  private void enableTrackHighlight() {
    if (mListView.getAdapter() == mRouteAdapter ||
        mMediaRouterPlayService == null || !isVisible())
      return;

    disableTrackHighlight();
    mCurrentTrackView = mListView.getChildAt(mMediaRouterPlayService.getCurrentTrack()
        - mListView.getFirstVisiblePosition() + mListView.getHeaderViewsCount());
    if (mCurrentTrackView != null) {
      mCurrentTrackView.setBackgroundColor(
          getResources().getColor(R.color.currently_playing_background));
    }
  }

  /**
   * Removes highlight from the item that was last highlighted.
   */
  private void disableTrackHighlight() {
    if (mCurrentTrackView != null) {
      mCurrentTrackView.setBackgroundColor(Color.TRANSPARENT);
    }
  }

  /**
   * Unselects current media renderer if one is selected (with dialog).
   */
  @Override
  public boolean onBackPressed() {
    if (mListView.getAdapter() == mPlaylistAdapter) {
      if (mPlaying) {
        new AlertDialog.Builder(getActivity())
            .setMessage(R.string.exit_renderer)
            .setPositiveButton(android.R.string.yes,
                new DialogInterface.OnClickListener() {
              @Override
                  public void onClick(DialogInterface dialog,
                            int which) {
                    mMediaRouterPlayService.stop();
                    changePlayPauseState(false);
                    deviceListMode();
                  }
                })
            .setNegativeButton(android.R.string.no, null)
            .show();
      }
      else {
        deviceListMode();
      }
      return true;
    }
    return false;
  }

  /**
   * Plays/pauses playback on button click.
   */
  @Override
  public void onClick(View v) {
    switch (v.getId()) {
    case R.id.playpause:
      if (mPlaying) {
        mMediaRouterPlayService.pause();
        changePlayPauseState(false);
      } else {
        mMediaRouterPlayService.resume();
        scrollToCurrent();
        changePlayPauseState(true);
      }
      break;
    case R.id.shuffle:
      mMediaRouterPlayService.toggleShuffleEnabled();
      applyColors();
      break;
    case R.id.previous:
      mPreviousTapCount++;
      Handler handler = new Handler();
      Runnable r = new Runnable() {

        @Override
        public void run() {
          // Single tap.
          mPreviousTapCount = 0;
          mMediaRouterPlayService.play(mMediaRouterPlayService.getCurrentTrack());
          changePlayPauseState(true);
        }
      };
      if (mPreviousTapCount == 1)
        handler.postDelayed(r, ViewConfiguration.getDoubleTapTimeout());
      else if(mPreviousTapCount == 2) {
        // Double tap.
        mPreviousTapCount = 0;
        mMediaRouterPlayService.playPrevious();
      }
      break;
    case R.id.next:
      boolean stillPlaying = mMediaRouterPlayService.playNext();
      changePlayPauseState(stillPlaying);
      break;
    case R.id.repeat:
      mMediaRouterPlayService.toggleRepeatEnabled();
      applyColors();
      break;
    }
  }

  /**
   * Enables or disables highlighting on shuffle/repeat buttons (depending
   * if they are enabled or disabled).
   */
  private void applyColors() {
    int highlight = getResources().getColor(R.color.button_highlight);
    int transparent = getResources().getColor(android.R.color.transparent);

    mShuffle.setColorFilter((mMediaRouterPlayService.getShuffleEnabled())
        ? highlight
        : transparent);
    mRepeat.setColorFilter((mMediaRouterPlayService.getRepeatEnabled())
        ? highlight
        : transparent);
  }

  /**
   * Sends manual seek on progress bar to renderer.
   */
  @Override
  public void onProgressChanged(SeekBar seekBar, int progress,
      boolean fromUser) {
    if (fromUser) {
      mMediaRouterPlayService.seek(progress);
    }
  }

  @Override
  public void onStartTrackingTouch(SeekBar seekBar) {
  }

  @Override
  public void onStopTrackingTouch(SeekBar seekBar) {
  }

  /**
   * Keeps track highlighting on the correct item while views are rebuilt.
   */
  @Override
  public void onScroll(AbsListView arg0, int arg1, int arg2, int arg3) {
    enableTrackHighlight();
  }

  @Override
  public void onScrollStateChanged(AbsListView arg0, int arg1) {
    enableTrackHighlight();
  }

  public void increaseVolume() {
    mMediaRouterPlayService.increaseVolume();
  }

  public void decreaseVolume() {
    mMediaRouterPlayService.decreaseVolume();
  }

  /**
   * Applies the playlist and starts playing at position.
   */
  public void play(List<Item> playlist, int start) {
    mPlaylistAdapter.clear();
    mPlaylistAdapter.add(playlist);
    mMediaRouterPlayService.setPlaylist(playlist);

    if (mSelectedRoute != null) {
      mMediaRouterPlayService.play(start);
      changePlayPauseState(true);
    }
    else {
      Toast.makeText(getActivity(), R.string.select_route, Toast.LENGTH_SHORT)
          .show();
      mStartPlayingOnSelect = start;
    }
  }

  /**
   * Generates a time string in the format mm:ss from a time value in seconds.
   *
   * @param time Time value in seconds (non-negative).
   * @return Formatted time string.
   */
  private String generateTimeString(int time) {
    int seconds = time % 60;
    int minutes = time / 60;
    if (minutes > 99) {
      return "99:99";
    }
    else {
      return Integer.toString(minutes) + ":" +
          ((seconds > 9)
              ? seconds
              : "0" + Integer.toString(seconds));
    }
  }

  /**
   * Receives information from MediaRouterPlayService about playback status.
   */
  public void receivePlaybackStatus(MediaItemStatus status) {
    // Views may not exist if fragment was just created/destroyed.
    if (getView() == null)
      return;

    int currentTime = (int) status.getContentPosition() / 1000;
    int totalTime = (int) status.getContentDuration() / 1000;

    mCurrentTimeView.setText(generateTimeString(currentTime));
    mTotalTimeView.setText(generateTimeString(totalTime));

    mProgressBar.setProgress(currentTime);
    mProgressBar.setMax(totalTime);

    if (status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_PLAYING ||
        status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_BUFFERING ||
        status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
      changePlayPauseState(true);
    }
    else {
      changePlayPauseState(false);
    }

    if (mListView.getAdapter() == mPlaylistAdapter) {
      enableTrackHighlight();
    }
  }

  /**
   * Changes the state of mPlayPause button to pause/resume according to
   * current playback state, also sets mPlaying.
   *
   * @param playing True if an item is currently being played, false otherwise.
   */
  private void changePlayPauseState(boolean playing) {
    mPlaying = playing;
    if (mPlaying) {
      mPlayPause.setImageResource(R.drawable.ic_action_pause);
      mPlayPause.setContentDescription(getResources().getString(R.string.pause));
    }
    else {
      mPlayPause.setImageResource(R.drawable.ic_action_play);
      mPlayPause.setContentDescription(getResources().getString(R.string.play));
    }
  }


  /**
   * When in playlist mode, scrolls to the item that is currently playing.
   */
  public void scrollToCurrent() {
    if (mMediaRouterPlayService != null) {
      mListView.smoothScrollToPosition(
          mMediaRouterPlayService.getCurrentTrack());
    }
  }

}




Java Source Code List

com.github.nutomic.controldlna.gui.MainActivity.java
com.github.nutomic.controldlna.gui.PreferencesActivity.java
com.github.nutomic.controldlna.gui.RouteFragment.java
com.github.nutomic.controldlna.gui.ServerFragment.java
com.github.nutomic.controldlna.localroute.Controller.java
com.github.nutomic.controldlna.localroute.ProviderService.java
com.github.nutomic.controldlna.localroute.Provider.java
com.github.nutomic.controldlna.mediarouter.MediaRouterPlayServiceBinder.java
com.github.nutomic.controldlna.mediarouter.MediaRouterPlayService.java
com.github.nutomic.controldlna.upnp.ProviderService.java
com.github.nutomic.controldlna.upnp.Provider.java
com.github.nutomic.controldlna.upnp.RemotePlayServiceBinder.java
com.github.nutomic.controldlna.upnp.RemotePlayService.java
com.github.nutomic.controldlna.utility.DeviceArrayAdapter.java
com.github.nutomic.controldlna.utility.FileArrayAdapter.java
com.github.nutomic.controldlna.utility.LoadImageTask.java
com.github.nutomic.controldlna.utility.RemoteImageView.java
com.github.nutomic.controldlna.utility.RouteAdapter.java