Android Open Source - controldlna Provider






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//ww w.  j av  a2s  .  co m
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.upnp;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.support.v7.media.MediaControlIntent;
import android.support.v7.media.MediaRouteDescriptor;
import android.support.v7.media.MediaRouteDiscoveryRequest;
import android.support.v7.media.MediaRouteProvider;
import android.support.v7.media.MediaRouteProviderDescriptor.Builder;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.ControlRequestCallback;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.widget.Toast;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Random;

/**
 * Allows playing to a DLNA renderer from a remote app.
 *
 * @author Felix Ableitner
 */
final class Provider extends MediaRouteProvider {

  private static final String TAG = "Provider";

  // Device has been added.
  // param: Device device
  public static final int MSG_RENDERER_ADDED = 1;

  // Device has been removed.
  // param: int id
  public static final int MSG_RENDERER_REMOVED = 2;

  // Playback status information, retrieved after RemotePlayService.MSG_GET_STATUS.
  // param: bundle media_item_status
  // param: int hash
  public static final int MSG_STATUS_INFO = 3;

  // Indicates an error in communication between RemotePlayService and renderer.
  // param: String error
  public static final int MSG_ERROR = 4;

  /**
   * Allows passing and storing basic information about a device.
   */
  static public class Device implements Parcelable {

    public String id;
    public String name;
    public String description;
    public int volume;
    public int volumeMax;

    public static final Parcelable.Creator<Device> CREATOR =
        new Parcelable.Creator<Device>() {
      public Device createFromParcel(Parcel in) {
        return new Device(in);
      }

      public Device[] newArray(int size) {
        return new Device[size];
      }
    };

    private Device(Parcel in) {
      id = in.readString();
      name = in.readString();
      description = in.readString();
      volume = in.readInt();
      volumeMax = in.readInt();
    }

    public Device(String id, String name, String description, int volume, int volumeMax) {
      this.id = id;
      this.name = name;
      this.description = description;
      this.volume = volume;
      this.volumeMax = volumeMax;
    }

    @Override
    public int describeContents() {
      return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
      dest.writeString(id);
      dest.writeString(name);
      dest.writeString(description);
      dest.writeInt(volume);
      dest.writeInt(volumeMax);
    }

  }

  private HashMap<String, Device> mDevices = new HashMap<String, Device>();

  private SparseArray<Pair<Intent, ControlRequestCallback>> mRequests =
      new SparseArray<Pair<Intent, ControlRequestCallback>>();

  IRemotePlayService mIRemotePlayService;

  private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName className, IBinder service) {
      mIRemotePlayService = IRemotePlayService.Stub.asInterface(service);
    }

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

  private static final ArrayList<IntentFilter> CONTROL_FILTERS;
  static {
    IntentFilter f = new IntentFilter();
    f.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
    f.addAction(MediaControlIntent.ACTION_PLAY);
    f.addAction(MediaControlIntent.ACTION_PAUSE);
    f.addAction(MediaControlIntent.ACTION_SEEK);
    f.addAction(MediaControlIntent.ACTION_STOP);
    f.addDataScheme("http");
    f.addDataScheme("https");
    try {
      f.addDataType("video/*");
      f.addDataType("audio/*");
    } catch (MalformedMimeTypeException ex) {
      throw new RuntimeException(ex);
    }

    CONTROL_FILTERS = new ArrayList<IntentFilter>();
    CONTROL_FILTERS.add(f);
  }

  /**
   * Listens for messages about devices.
   */
  static private class DeviceListener extends Handler {

    private final WeakReference<Provider> mService;

    DeviceListener(Provider provider) {
      mService = new WeakReference<Provider>(provider);
    }

    @Override
    public void handleMessage(Message msg) {
      if (mService.get() != null) {
        mService.get().handleMessage(msg);
      }
    }
  }

  final Messenger mListener = new Messenger(new DeviceListener(this));

  public Provider(Context context) {
    super(context);
    context.bindService(new Intent(context, RemotePlayService.class),
        mConnection, Context.BIND_AUTO_CREATE);
  }

  public void close() {
    getContext().unbindService(mConnection);
  }

  @Override
  public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
    try {
      if (request != null && request.isActiveScan() && mIRemotePlayService != null) {
        mIRemotePlayService.startSearch(mListener);
      }
    }
    catch (RemoteException e) {
      e.printStackTrace();
    }
  }

  @Override
  public RouteController onCreateRouteController(String routeId) {
    return new RouteController(routeId);
  }

  private void updateRoutes() {
    Builder builder = new Builder();
    for (Entry<String, Device> d : mDevices.entrySet()) {
      MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder(
              d.getValue().id, d.getValue().name)
          .setDescription(d.getValue().description)
          .addControlFilters(CONTROL_FILTERS)
          .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
          .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
          .setVolumeMax(d.getValue().volumeMax)
          .setVolume(d.getValue().volume)
          .build();
      builder.addRoute(routeDescriptor);
    }
    setDescriptor(builder.build());
  }

  /**
   * Receives and forwards device selections, volume change
   * requests and control requests.
   */
  private final class RouteController extends MediaRouteProvider.RouteController {

    private final String mRouteId;

    public RouteController(String routeId) {
      mRouteId = routeId;
    }

    @Override
    public void onRelease() {
    }

    @Override
    public void onSelect() {
      try {
        mIRemotePlayService.selectRenderer(mRouteId);
      }
      catch (RemoteException e) {
        e.printStackTrace();
      }
    }

    @Override
    public void onUnselect() {
      try {
        mIRemotePlayService.unselectRenderer(mRouteId);
      }
      catch (RemoteException e) {
        e.printStackTrace();
      }
    }

    @Override
    public void onSetVolume(int volume) {
      if (volume < 0 || volume > mDevices.get(mRouteId).volumeMax)
        return;

      try {
        mIRemotePlayService.setVolume(volume);
      }
      catch (RemoteException e) {
        e.printStackTrace();
      }
      mDevices.get(mRouteId).volume = volume;
      updateRoutes();
    }

    @Override
    public void onUpdateVolume(int delta) {
      onSetVolume(mDevices.get(mRouteId).volume + delta);
    }

    /**
     * Handles play, pause, resume, stop, seek and get_status requests for this route.
     */
    @Override
    public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
      try {
        if (intent.getAction().equals(MediaControlIntent.ACTION_PLAY)) {
          String metadata = (intent.hasExtra(MediaControlIntent.EXTRA_ITEM_METADATA))
              ? intent.getExtras().getString(MediaControlIntent.EXTRA_ITEM_METADATA)
              : null;
          mIRemotePlayService.play(intent.getDataString(), metadata);
          // Store in intent extras for later.
          intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mRouteId);
          intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, intent.getDataString());
          getItemStatus(intent, callback);
          return true;
        }
        else if (intent.getAction().equals(MediaControlIntent.ACTION_PAUSE)) {
          mIRemotePlayService.pause(mRouteId);
          return true;
        }
        else if (intent.getAction().equals(MediaControlIntent.ACTION_RESUME)) {
          mIRemotePlayService.resume(mRouteId);
          return true;
        }
        else if (intent.getAction().equals(MediaControlIntent.ACTION_STOP)) {
          mIRemotePlayService.stop(mRouteId);
          return true;
        }
        else if (intent.getAction().equals(MediaControlIntent.ACTION_SEEK)) {
          mIRemotePlayService.seek(mRouteId,
              intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID),
              intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0));
          getItemStatus(intent, callback);
          return true;
        }
        else if(intent.getAction().equals(MediaControlIntent.ACTION_GET_STATUS)) {
          getItemStatus(intent, callback);
          return true;
        }
      }
      catch (RemoteException e) {
        Log.w(TAG, "Failed to execute control request", e);
      }
      return false;
    }

  }

  /**
   * Requests status info via RemotePlayService, stores intent and callback to
   * access later in handleMessage.
   */
  private void getItemStatus(Intent intent, ControlRequestCallback callback)
      throws RemoteException {
    if (callback == null)
      return;

    Pair<Intent, ControlRequestCallback> pair =
        new Pair<Intent, ControlRequestCallback>(intent, callback);
    int r = new Random().nextInt();
    mRequests.put(r, pair);
    mIRemotePlayService.getItemStatus(
        intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID),
        intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID), r);
  }

  /**
   * Handles device add and remove as well as sending status info requested earlier.
   */
  public void handleMessage(Message msg) {
    Bundle data = msg.getData();
    switch (msg.what) {
    case MSG_RENDERER_ADDED:
      msg.getData().setClassLoader(Device.class.getClassLoader());
      Device device = (Device) data.getParcelable("device");
      mDevices.put(device.id, device);
      updateRoutes();
      break;
    case MSG_RENDERER_REMOVED:
      mDevices.remove(data.getString("id"));
      updateRoutes();
      break;
    case MSG_STATUS_INFO:
      Pair<Intent, ControlRequestCallback> pair =
      mRequests.get(data.getInt("hash"));
      Bundle status = data.getBundle("media_item_status");

      if (pair.first.hasExtra(MediaControlIntent.EXTRA_SESSION_ID)) {
        status.putString(MediaControlIntent.EXTRA_SESSION_ID,
            pair.first.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID));
      }
      if (pair.first.hasExtra(MediaControlIntent.EXTRA_ITEM_ID)) {
        status.putString(MediaControlIntent.EXTRA_ITEM_ID,
            pair.first.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID));
      }
      pair.second.onResult(status);
      break;
    case MSG_ERROR:
      Toast.makeText(getContext(), data.getString("error"), Toast.LENGTH_SHORT).show();
      break;
    }

  }

}




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