1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.supportv7.media;
18 
19 import android.annotation.TargetApi;
20 import android.content.Context;
21 import android.graphics.Bitmap;
22 import android.os.Build;
23 import android.support.v4.media.MediaMetadataCompat;
24 import android.support.v4.media.session.MediaSessionCompat;
25 import android.support.v4.media.session.PlaybackStateCompat;
26 import android.support.v7.media.MediaControlIntent;
27 import android.support.v7.media.MediaRouter.RouteInfo;
28 import android.util.Log;
29 
30 /**
31  * Abstraction of common playback operations of media items, such as play,
32  * seek, etc. Used by PlaybackManager as a backend to handle actual playback
33  * of media items.
34  *
35  * TODO: Introduce prepare() method and refactor subclasses accordingly.
36  */
37 public abstract class Player {
38     private static final String TAG = "SampleMediaRoutePlayer";
39     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
40     protected static final int STATE_IDLE = 0;
41     protected static final int STATE_PREPARING_FOR_PLAY = 1;
42     protected static final int STATE_PREPARING_FOR_PAUSE = 2;
43     protected static final int STATE_READY = 3;
44     protected static final int STATE_PLAYING = 4;
45     protected static final int STATE_PAUSED = 5;
46 
47     private static final long PLAYBACK_ACTIONS = PlaybackStateCompat.ACTION_PAUSE
48             | PlaybackStateCompat.ACTION_PLAY;
49     private static final PlaybackStateCompat INIT_PLAYBACK_STATE = new PlaybackStateCompat.Builder()
50             .setState(PlaybackStateCompat.STATE_NONE, 0, .0f).build();
51 
52     protected Callback mCallback;
53     protected MediaSessionCompat mMediaSession;
54 
isRemotePlayback()55     public abstract boolean isRemotePlayback();
isQueuingSupported()56     public abstract boolean isQueuingSupported();
57 
connect(RouteInfo route)58     public abstract void connect(RouteInfo route);
release()59     public abstract void release();
60 
61     // basic operations that are always supported
play(final PlaylistItem item)62     public abstract void play(final PlaylistItem item);
seek(final PlaylistItem item)63     public abstract void seek(final PlaylistItem item);
getStatus(final PlaylistItem item, final boolean update)64     public abstract void getStatus(final PlaylistItem item, final boolean update);
pause()65     public abstract void pause();
resume()66     public abstract void resume();
stop()67     public abstract void stop();
68 
69     // advanced queuing (enqueue & remove) are only supported
70     // if isQueuingSupported() returns true
enqueue(final PlaylistItem item)71     public abstract void enqueue(final PlaylistItem item);
remove(String iid)72     public abstract PlaylistItem remove(String iid);
73 
takeSnapshot()74     public void takeSnapshot() {}
getSnapshot()75     public Bitmap getSnapshot() { return null; }
76 
77     /**
78      * presentation display
79      */
80     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
updatePresentation()81     public void updatePresentation() {}
82 
setCallback(Callback callback)83     public void setCallback(Callback callback) {
84         mCallback = callback;
85     }
86 
create(Context context, RouteInfo route, MediaSessionCompat session)87     public static Player create(Context context, RouteInfo route, MediaSessionCompat session) {
88         Player player;
89         if (route != null && route.supportsControlCategory(
90                 MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
91             player = new RemotePlayer(context);
92         } else if (route != null) {
93             player = new LocalPlayer.SurfaceViewPlayer(context);
94         } else {
95             player = new LocalPlayer.OverlayPlayer(context);
96         }
97         player.setMediaSession(session);
98         player.initMediaSession();
99         player.connect(route);
100         return player;
101     }
102 
initMediaSession()103     protected void initMediaSession() {
104         if (mMediaSession == null) {
105             return;
106         }
107         mMediaSession.setMetadata(null);
108         mMediaSession.setPlaybackState(INIT_PLAYBACK_STATE);
109     }
110 
updateMetadata(PlaylistItem currentItem)111     protected void updateMetadata(PlaylistItem currentItem) {
112         if (mMediaSession == null) {
113             return;
114         }
115         if (DEBUG) {
116             Log.d(TAG, "Update metadata: currentItem=" + currentItem);
117         }
118         if (currentItem == null) {
119             mMediaSession.setMetadata(null);
120             return;
121         }
122         MediaMetadataCompat.Builder bob = new MediaMetadataCompat.Builder();
123         bob.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, currentItem.getTitle());
124         bob.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Subtitle of the thing");
125         bob.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION,
126                 "Description of the thing");
127         bob.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, getSnapshot());
128         mMediaSession.setMetadata(bob.build());
129     }
130 
publishState(int state)131     protected void publishState(int state) {
132         if (mMediaSession == null) {
133             return;
134         }
135         PlaybackStateCompat.Builder bob = new PlaybackStateCompat.Builder();
136         bob.setActions(PLAYBACK_ACTIONS);
137         switch (state) {
138             case STATE_PLAYING:
139                 bob.setState(PlaybackStateCompat.STATE_PLAYING, -1, 1);
140                 break;
141             case STATE_READY:
142             case STATE_PAUSED:
143                 bob.setState(PlaybackStateCompat.STATE_PAUSED, -1, 0);
144                 break;
145             case STATE_PREPARING_FOR_PLAY:
146             case STATE_PREPARING_FOR_PAUSE:
147                 bob.setState(PlaybackStateCompat.STATE_BUFFERING, -1, 0);
148                 break;
149             case STATE_IDLE:
150                 bob.setState(PlaybackStateCompat.STATE_STOPPED, -1, 0);
151                 break;
152         }
153         PlaybackStateCompat pbState = bob.build();
154         Log.d(TAG, "Setting state to " + pbState);
155         mMediaSession.setPlaybackState(pbState);
156         if (state != STATE_IDLE) {
157             mMediaSession.setActive(true);
158         } else {
159             mMediaSession.setActive(false);
160         }
161     }
162 
setMediaSession(MediaSessionCompat session)163     private void setMediaSession(MediaSessionCompat session) {
164         mMediaSession = session;
165     }
166 
167     public interface Callback {
onError()168         void onError();
onCompletion()169         void onCompletion();
onPlaylistChanged()170         void onPlaylistChanged();
onPlaylistReady()171         void onPlaylistReady();
172     }
173 }
174