Merged 'localroute' project.
This commit is contained in:
parent
7eba914165
commit
5ca26af0e4
5 changed files with 361 additions and 0 deletions
|
@ -66,6 +66,13 @@
|
|||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name=".localroute.ProviderService"
|
||||
android:process=":mrp">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.MediaRouteProviderService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
<string name="enable_wifi_dialog">Wifi needs to be connected for playback.
|
||||
Do you want to enable it now?</string>
|
||||
|
||||
<!-- Description for local playback device -->
|
||||
<string name="local_device">Local Device</string>
|
||||
|
||||
<!-- Image alt text -->
|
||||
<string name="album_art">Album Art</string>
|
||||
|
||||
|
|
197
src/com/github/nutomic/controldlna/localroute/Controller.java
Normal file
197
src/com/github/nutomic/controldlna/localroute/Controller.java
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
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:
|
||||
* 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.localroute;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v7.media.MediaControlIntent;
|
||||
import android.support.v7.media.MediaItemStatus;
|
||||
import android.support.v7.media.MediaRouteProvider;
|
||||
import android.support.v7.media.MediaRouter.ControlRequestCallback;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Receives control intents through media route and executes them on a MediaPlayer.
|
||||
*
|
||||
* @author felix
|
||||
*
|
||||
*/
|
||||
public class Controller extends MediaRouteProvider.RouteController implements
|
||||
MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener {
|
||||
|
||||
private static final String TAG = "Controller";
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private AudioManager mAudio;
|
||||
|
||||
AudioManager.OnAudioFocusChangeListener mFocusListener;
|
||||
|
||||
private final String mRouteId;
|
||||
|
||||
private String mItemId;
|
||||
|
||||
private int mState;
|
||||
|
||||
private MediaPlayer mPlayer = new MediaPlayer();
|
||||
|
||||
public Controller(String routeId, Context context) {
|
||||
mContext = context;
|
||||
mRouteId = routeId;
|
||||
mAudio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||
mPlayer.setOnPreparedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease() {
|
||||
mPlayer.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelect() {
|
||||
mAudio.requestAudioFocus(mFocusListener, AudioManager.STREAM_MUSIC,
|
||||
AudioManager.AUDIOFOCUS_GAIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnselect() {
|
||||
mAudio.abandonAudioFocus(mFocusListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetVolume(int volume) {
|
||||
mAudio.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateVolume(int delta) {
|
||||
int currentVolume = mAudio.getStreamVolume(AudioManager.STREAM_MUSIC);
|
||||
mAudio.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume + delta, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
|
||||
String sessionId = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
|
||||
String itemId = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
|
||||
if (intent.getAction().equals(MediaControlIntent.ACTION_PLAY)) {
|
||||
try {
|
||||
mPlayer.reset();
|
||||
mPlayer.setDataSource(mContext, intent.getData());
|
||||
mPlayer.prepareAsync();
|
||||
mItemId = intent.getDataString();
|
||||
mState = MediaItemStatus.PLAYBACK_STATE_BUFFERING;
|
||||
getStatus(mItemId, mRouteId, callback);
|
||||
return true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
mState = MediaItemStatus.PLAYBACK_STATE_ERROR;
|
||||
Log.d(TAG, "Failed to start playback", e);
|
||||
} catch (IOException e) {
|
||||
mState = MediaItemStatus.PLAYBACK_STATE_ERROR;
|
||||
Log.d(TAG, "Failed to start playback", e);
|
||||
}
|
||||
}
|
||||
else if (intent.getAction().equals(MediaControlIntent.ACTION_PAUSE)) {
|
||||
mPlayer.pause();
|
||||
mState = MediaItemStatus.PLAYBACK_STATE_PAUSED;
|
||||
return true;
|
||||
}
|
||||
else if (intent.getAction().equals(MediaControlIntent.ACTION_RESUME)) {
|
||||
mPlayer.start();
|
||||
mState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
|
||||
return true;
|
||||
}
|
||||
else if (intent.getAction().equals(MediaControlIntent.ACTION_STOP)) {
|
||||
mPlayer.stop();
|
||||
mState = MediaItemStatus.PLAYBACK_STATE_CANCELED;
|
||||
return true;
|
||||
}
|
||||
else if (intent.getAction().equals(MediaControlIntent.ACTION_SEEK)) {
|
||||
mPlayer.seekTo((int) intent.getLongExtra(
|
||||
MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0));
|
||||
getStatus(itemId, sessionId, callback);
|
||||
return true;
|
||||
}
|
||||
else if(intent.getAction().equals(MediaControlIntent.ACTION_GET_STATUS)) {
|
||||
getStatus(itemId, sessionId, callback);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void getStatus(String itemId, String sessionId, ControlRequestCallback callback) {
|
||||
if (callback == null)
|
||||
return;
|
||||
|
||||
Bundle status = null;
|
||||
|
||||
if (mItemId.equals(itemId)) {
|
||||
status = new MediaItemStatus.Builder(mState)
|
||||
.setContentPosition(mPlayer.getCurrentPosition())
|
||||
.setContentDuration(mPlayer.getDuration())
|
||||
.setTimestamp(SystemClock.elapsedRealtime())
|
||||
.build().asBundle();
|
||||
|
||||
status.putString(MediaControlIntent.EXTRA_SESSION_ID, mRouteId);
|
||||
status.putString(MediaControlIntent.EXTRA_ITEM_ID, mItemId);
|
||||
}
|
||||
else
|
||||
status = new MediaItemStatus.Builder(MediaItemStatus.PLAYBACK_STATE_INVALIDATED)
|
||||
.build().asBundle();
|
||||
|
||||
callback.onResult(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets state to finished.
|
||||
*
|
||||
* Note: Do not set the listener before play() is called
|
||||
* (or this will be called immediately).
|
||||
*/
|
||||
@Override
|
||||
public void onCompletion(MediaPlayer mp) {
|
||||
mState = MediaItemStatus.PLAYBACK_STATE_FINISHED;
|
||||
mPlayer.setOnCompletionListener(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts playback and sets completion listener.
|
||||
*/
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
mPlayer.start();
|
||||
mState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
|
||||
mPlayer.setOnCompletionListener(this);
|
||||
}
|
||||
}
|
105
src/com/github/nutomic/controldlna/localroute/Provider.java
Normal file
105
src/com/github/nutomic/controldlna/localroute/Provider.java
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
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:
|
||||
* 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.localroute;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.IntentFilter.MalformedMimeTypeException;
|
||||
import android.content.res.Resources;
|
||||
import android.media.AudioManager;
|
||||
import android.support.v7.media.MediaControlIntent;
|
||||
import android.support.v7.media.MediaRouteDescriptor;
|
||||
import android.support.v7.media.MediaRouteProvider;
|
||||
import android.support.v7.media.MediaRouteProviderDescriptor;
|
||||
import android.support.v7.media.MediaRouter;
|
||||
|
||||
import com.github.nutomic.controldlna.R;
|
||||
|
||||
/**
|
||||
* MediaRouteProvider that details the local audio route with its
|
||||
* controls to the system.
|
||||
*
|
||||
* @author felix
|
||||
*
|
||||
*/
|
||||
final class Provider extends MediaRouteProvider {
|
||||
|
||||
private static final String ROUTE_ID = "local_route";
|
||||
|
||||
AudioManager mAudio = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
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");
|
||||
addDataTypeUnchecked(f, "audio/*");
|
||||
|
||||
CONTROL_FILTERS = new ArrayList<IntentFilter>();
|
||||
CONTROL_FILTERS.add(f);
|
||||
}
|
||||
|
||||
private static void addDataTypeUnchecked(IntentFilter filter, String type) {
|
||||
try {
|
||||
filter.addDataType(type);
|
||||
} catch (MalformedMimeTypeException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public Provider(Context context) {
|
||||
super(context);
|
||||
|
||||
MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder(
|
||||
ROUTE_ID, context.getResources().getString(R.string.local_device))
|
||||
.addControlFilters(CONTROL_FILTERS)
|
||||
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
|
||||
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
|
||||
.setVolume(mAudio.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
|
||||
.build();
|
||||
|
||||
|
||||
MediaRouteProviderDescriptor providerDescriptor =
|
||||
new MediaRouteProviderDescriptor.Builder()
|
||||
.addRoute(routeDescriptor)
|
||||
.build();
|
||||
setDescriptor(providerDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouteController onCreateRouteController(String routeId) {
|
||||
return new Controller(routeId, getContext());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
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:
|
||||
* 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.localroute;
|
||||
|
||||
import android.support.v7.media.MediaRouteProvider;
|
||||
import android.support.v7.media.MediaRouteProviderService;
|
||||
|
||||
public class ProviderService extends MediaRouteProviderService {
|
||||
|
||||
private Provider mProvider;
|
||||
|
||||
@Override
|
||||
public MediaRouteProvider onCreateMediaRouteProvider() {
|
||||
if (mProvider == null)
|
||||
mProvider = new Provider(this);
|
||||
return mProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mProvider = null;
|
||||
}
|
||||
}
|
Reference in a new issue