Java tutorial
/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.appdevper.mediaplayer.app; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.media.MediaMetadata; import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaButtonReceiver; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.support.v7.media.MediaRouter; import android.util.Log; import com.appdevper.mediaplayer.R; import com.appdevper.mediaplayer.activity.MainActivity; import com.appdevper.mediaplayer.model.MusicProvider; import com.appdevper.mediaplayer.util.AlbumArtCache; import com.appdevper.mediaplayer.util.LogHelper; import com.appdevper.mediaplayer.util.MediaIDHelper; import com.appdevper.mediaplayer.util.QueueHelper; import com.google.android.gms.cast.ApplicationMetadata; import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager; import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; public class MusicService extends Service implements PlaybackManager.PlaybackServiceCallback { // Extra on MediaSession that contains the Cast device name currently connected to public static final String EXTRA_CONNECTED_CAST = "com.appdevper.mediaplayer.CAST_NAME"; // The action of the incoming Intent indicating that it contains a command // to be executed (see {@link #onStartCommand}) public static final String ACTION_CMD = "com.appdevper.mediaplayer.ACTION_CMD"; // The key in the extras of the incoming Intent indicating the command that // should be executed (see {@link #onStartCommand}) public static final String CMD_NAME = "CMD_NAME"; // A value of a CMD_NAME key in the extras of the incoming Intent that // indicates that the music playback should be paused (see {@link #onStartCommand}) public static final String CMD_PAUSE = "CMD_PAUSE"; // A value of a CMD_NAME key that indicates that the music playback should switch // to local playback from cast playback. public static final String CMD_STOP_CASTING = "CMD_STOP_CASTING"; private static final String TAG = LogHelper.makeLogTag(MusicService.class); // Action to thumbs up a media item private static final String CUSTOM_ACTION_THUMBS_UP = "com.appdevper.mediaplayer.THUMBS_UP"; // Delay stopSelf by using a handler. private static final int STOP_DELAY = 30000; // Music catalog manager private PlaybackManager mPlaybackManager; private MusicProvider mMusicProvider; private MediaSessionCompat mSession; // "Now playing" queue: private List<MediaSessionCompat.QueueItem> mPlayingQueue; private int mCurrentIndexOnQueue; private MediaNotificationManager mMediaNotificationManager; // Indicates whether the service was started. private boolean mServiceStarted; private Bundle mSessionExtras; private final DelayedStopHandler mDelayedStopHandler = new DelayedStopHandler(this); private MediaRouter mMediaRouter; private final IBinder mBinder = new LocalBinder(); private final VideoCastConsumerImpl mCastConsumer = new VideoCastConsumerImpl() { @Override public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { // In case we are casting, send the device name as an extra on MediaSession metadata. mSessionExtras.putString(EXTRA_CONNECTED_CAST, mCastManager.getDeviceName()); mSession.setExtras(mSessionExtras); // Now we can switch to CastPlayback Playback playback = new CastPlayback(mMusicProvider); mMediaRouter.setMediaSession(mSession); mPlaybackManager.switchToPlayback(playback, true); } @Override public void onDisconnectionReason(int reason) { LogHelper.d(TAG, "onDisconnectionReason"); // This is our final chance to update the underlying stream position // In onDisconnected(), the underlying CastPlayback#mVideoCastConsumer // is disconnected and hence we update our local value of stream position // to the latest position. mPlaybackManager.getPlayback().updateLastKnownStreamPosition(); } @Override public void onDisconnected() { Log.d(TAG, "onDisconnected"); mSessionExtras.remove(EXTRA_CONNECTED_CAST); mSession.setExtras(mSessionExtras); Playback playback = new LocalPlayback(MusicService.this, mMusicProvider); mMediaRouter.setMediaSession(null); mPlaybackManager.switchToPlayback(playback, false); } }; private VideoCastManager mCastManager; private Handler mHandler; public void connectUpnp(String deviceName) { Log.d(TAG, "connectUpnp"); mSessionExtras.putString(EXTRA_CONNECTED_CAST, deviceName); mSession.setExtras(mSessionExtras); Playback playback = new UpnpPlayback(mMusicProvider); mMediaRouter.setMediaSession(mSession); mPlaybackManager.switchToPlayback(playback, true); } public void disconnectUpnp() { Log.d(TAG, "disconnectUpnp"); mSessionExtras.remove(EXTRA_CONNECTED_CAST); mSession.setExtras(mSessionExtras); Playback playback = new LocalPlayback(MusicService.this, mMusicProvider); mMediaRouter.setMediaSession(null); mPlaybackManager.switchToPlayback(playback, false); } /* * (non-Javadoc) * @see android.app.Service#onCreate() */ @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate"); mPlayingQueue = new ArrayList<>(); mMusicProvider = MusicProvider.getInstance(); QueueManager queueManager = new QueueManager(mMusicProvider, getResources(), new QueueManager.MetadataUpdateListener() { @Override public void onMetadataChanged(MediaMetadataCompat metadata) { mSession.setMetadata(metadata); } @Override public void onMetadataRetrieveError() { mPlaybackManager.updatePlaybackState(getString(R.string.error_no_metadata)); } @Override public void onCurrentQueueIndexUpdated(int queueIndex) { mPlaybackManager.handlePlayRequest(); } @Override public void onQueueUpdated(String title, List<MediaSessionCompat.QueueItem> newQueue) { mSession.setQueue(newQueue); mSession.setQueueTitle(title); } }); LocalPlayback playback = new LocalPlayback(this, mMusicProvider); mPlaybackManager = new PlaybackManager(this, getResources(), mMusicProvider, queueManager, playback); // Start a new MediaSession mSession = new MediaSessionCompat(this, "MusicService"); //setSessionToken(mSession.getSessionToken()); mSession.setCallback(mPlaybackManager.getMediaSessionCallback()); mSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); Context context = getApplicationContext(); Intent intent = new Intent(context, MainActivity.class); PendingIntent pi = PendingIntent.getActivity(context, 99, intent, PendingIntent.FLAG_UPDATE_CURRENT); mSession.setSessionActivity(pi); mSessionExtras = new Bundle(); mSession.setExtras(mSessionExtras); mPlaybackManager.updatePlaybackState(null); try { mMediaNotificationManager = new MediaNotificationManager(this); } catch (RemoteException e) { throw new IllegalStateException("Could not create a MediaNotificationManager", e); } VideoCastManager.getInstance().addVideoCastConsumer(mCastConsumer); mMediaRouter = MediaRouter.getInstance(getApplicationContext()); } /** * (non-Javadoc) * * @see android.app.Service#onStartCommand(Intent, int, int) */ @Override public int onStartCommand(Intent startIntent, int flags, int startId) { if (startIntent != null) { String action = startIntent.getAction(); String command = startIntent.getStringExtra(CMD_NAME); if (ACTION_CMD.equals(action)) { if (CMD_PAUSE.equals(command)) { mPlaybackManager.handlePauseRequest(); } else if (CMD_STOP_CASTING.equals(command)) { VideoCastManager.getInstance().disconnect(); } } else { // Try to handle the intent as a media button event wrapped by MediaButtonReceiver MediaButtonReceiver.handleIntent(mSession, startIntent); } } // Reset the delay handler to enqueue a message to stop the service if // nothing is playing. mDelayedStopHandler.removeCallbacksAndMessages(null); mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY); return START_STICKY; } /** * (non-Javadoc) * * @see android.app.Service#onDestroy() */ @Override public void onDestroy() { LogHelper.d(TAG, "onDestroy"); // Service is being killed, so make sure we release our resources mPlaybackManager.handleStopRequest(null); mMediaNotificationManager.stopNotification(); VideoCastManager.getInstance().removeVideoCastConsumer(mCastConsumer); mDelayedStopHandler.removeCallbacksAndMessages(null); mSession.release(); } @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } public MediaSessionCompat.Token getSessionToken() { return mSession.getSessionToken(); } /** * Callback method called from PlaybackManager whenever the music is about to play. */ @Override public void onPlaybackStart() { if (!mSession.isActive()) { mSession.setActive(true); } mDelayedStopHandler.removeCallbacksAndMessages(null); // The service needs to continue running even after the bound client (usually a // MediaController) disconnects, otherwise the music playback will stop. // Calling startService(Intent) will keep the service running until it is explicitly killed. startService(new Intent(getApplicationContext(), MusicService.class)); } /** * Callback method called from PlaybackManager whenever the music stops playing. */ @Override public void onPlaybackStop() { // Reset the delayed stop handler, so after STOP_DELAY it will be executed again, // potentially stopping the service. mDelayedStopHandler.removeCallbacksAndMessages(null); mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY); stopForeground(true); } @Override public void onNotificationRequired() { mMediaNotificationManager.startNotification(); } @Override public void onPlaybackStateUpdated(PlaybackStateCompat newState) { mSession.setPlaybackState(newState); } public class LocalBinder extends Binder { public MusicService getService() { return MusicService.this; } } /** * A simple handler that stops the service if playback is not active (playing) */ private static class DelayedStopHandler extends Handler { private final WeakReference<MusicService> mWeakReference; private DelayedStopHandler(MusicService service) { mWeakReference = new WeakReference<>(service); } @Override public void handleMessage(Message msg) { MusicService service = mWeakReference.get(); if (service != null && service.mPlaybackManager.getPlayback() != null) { if (service.mPlaybackManager.getPlayback().isPlaying()) { LogHelper.d(TAG, "Ignoring delayed stop since the media player is in use."); return; } LogHelper.d(TAG, "Stopping service with delay handler."); service.stopSelf(); } } } public interface UpnpCallBack { void onConnect(); void onDisConnect(); } }