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