1/*
2 * Copyright (C) 2014 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 */
16package com.android.onemedia;
17
18import android.content.Context;
19import android.content.Intent;
20import android.graphics.Bitmap;
21import android.media.MediaMetadata;
22import android.media.session.MediaSession;
23import android.media.session.MediaSession.QueueItem;
24import android.media.session.MediaSessionManager;
25import android.media.session.PlaybackState;
26import android.os.Bundle;
27import android.os.RemoteException;
28import android.os.SystemClock;
29import android.util.Log;
30import android.view.KeyEvent;
31
32import com.android.onemedia.playback.LocalRenderer;
33import com.android.onemedia.playback.Renderer;
34import com.android.onemedia.playback.RequestUtils;
35
36import java.util.ArrayList;
37import java.util.List;
38
39public class PlayerSession {
40    private static final String TAG = "PlayerSession";
41
42    protected MediaSession mSession;
43    protected Context mContext;
44    protected Renderer mRenderer;
45    protected MediaSession.Callback mCallback;
46    protected Renderer.Listener mRenderListener;
47    protected MediaMetadata.Builder mMetadataBuilder;
48    protected ArrayList<MediaSession.QueueItem> mQueue;
49    protected boolean mUseQueue;
50
51    protected PlaybackState mPlaybackState;
52    protected Listener mListener;
53
54    private String mContent;
55
56    public PlayerSession(Context context) {
57        mContext = context;
58        mRenderer = new LocalRenderer(context, null);
59        mCallback = new SessionCb();
60        mRenderListener = new RenderListener();
61        PlaybackState.Builder psBob = new PlaybackState.Builder();
62        psBob.setActions(PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY);
63        mPlaybackState = psBob.build();
64        mQueue = new ArrayList<MediaSession.QueueItem>();
65
66        mRenderer.registerListener(mRenderListener);
67
68        initMetadata();
69    }
70
71    public void createSession() {
72        releaseSession();
73
74        MediaSessionManager man = (MediaSessionManager) mContext
75                .getSystemService(Context.MEDIA_SESSION_SERVICE);
76        Log.d(TAG, "Creating session for package " + mContext.getBasePackageName());
77
78        mSession = new MediaSession(mContext, "OneMedia");
79        mSession.setCallback(mCallback);
80        mSession.setPlaybackState(mPlaybackState);
81        mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
82                | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
83        mSession.setActive(true);
84        updateMetadata();
85    }
86
87    public void onDestroy() {
88        releaseSession();
89        if (mRenderer != null) {
90            mRenderer.unregisterListener(mRenderListener);
91            mRenderer.onDestroy();
92        }
93    }
94
95    private void releaseSession() {
96        if (mSession != null) {
97            mSession.release();
98            mSession = null;
99        }
100    }
101
102    public void setListener(Listener listener) {
103        mListener = listener;
104    }
105
106    public MediaSession.Token getSessionToken() {
107        return mSession.getSessionToken();
108    }
109
110    public void setContent(Bundle request) {
111        mRenderer.setContent(request);
112        mContent = request.getString(RequestUtils.EXTRA_KEY_SOURCE);
113    }
114
115    public void setNextContent(Bundle request) {
116        mRenderer.setNextContent(request);
117    }
118
119    public void setIcon(Bitmap icon) {
120        mMetadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, icon);
121        mQueue.clear();
122        mQueue.add(new QueueItem(mMetadataBuilder.build().getDescription(), 11));
123        updateMetadata();
124    }
125
126    private void updateMetadata() {
127        // This is a mild abuse of metadata and shouldn't be duplicated in real
128        // code
129        if (mSession != null && mSession.isActive()) {
130            mSession.setMetadata(mMetadataBuilder.build());
131            // Just toggle the queue every time we update for testing
132            mSession.setQueue(mUseQueue ? mQueue : null);
133            mSession.setQueueTitle(mUseQueue ? "Queue title" : null);
134            mUseQueue = !mUseQueue;
135        }
136    }
137
138    private void updateState(int newState) {
139        float rate = newState == PlaybackState.STATE_PLAYING ? 1 : 0;
140        long position = mRenderer == null ? -1 : mRenderer.getSeekPosition();
141        PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
142        bob.setState(newState, position, rate, SystemClock.elapsedRealtime());
143        bob.setErrorMessage(null);
144        mPlaybackState = bob.build();
145        mSession.setPlaybackState(mPlaybackState);
146    }
147
148    private void initMetadata() {
149        mMetadataBuilder = new MediaMetadata.Builder();
150        mMetadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE,
151                "OneMedia display title");
152        mMetadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
153                "OneMedia display subtitle");
154
155        mQueue.add(new QueueItem(mMetadataBuilder.build().getDescription(), 11));
156    }
157
158    public interface Listener {
159        public void onPlayStateChanged(PlaybackState state);
160    }
161
162    private class RenderListener implements Renderer.Listener {
163
164        @Override
165        public void onError(int type, int extra, Bundle extras, Throwable error) {
166            Log.d(TAG, "Sending onError with type " + type + " and extra " + extra);
167            PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
168            bob.setState(PlaybackState.STATE_ERROR, -1, 0, 0);
169            if (error != null) {
170                bob.setErrorMessage(error.getLocalizedMessage());
171            }
172            mPlaybackState = bob.build();
173            mSession.setPlaybackState(mPlaybackState);
174            if (mListener != null) {
175                mListener.onPlayStateChanged(mPlaybackState);
176            }
177        }
178
179        @Override
180        public void onStateChanged(int newState) {
181            long position = -1;
182            if (mRenderer != null) {
183                position = mRenderer.getSeekPosition();
184            }
185            int pbState;
186            float rate = 0;
187            String errorMsg = null;
188            switch (newState) {
189                case Renderer.STATE_ENDED:
190                case Renderer.STATE_STOPPED:
191                    pbState = PlaybackState.STATE_STOPPED;
192                    break;
193                case Renderer.STATE_INIT:
194                case Renderer.STATE_PREPARING:
195                    pbState = PlaybackState.STATE_BUFFERING;
196                    break;
197                case Renderer.STATE_ERROR:
198                    pbState = PlaybackState.STATE_ERROR;
199                    break;
200                case Renderer.STATE_PAUSED:
201                    pbState = PlaybackState.STATE_PAUSED;
202                    break;
203                case Renderer.STATE_PLAYING:
204                    pbState = PlaybackState.STATE_PLAYING;
205                    rate = 1;
206                    break;
207                default:
208                    pbState = PlaybackState.STATE_ERROR;
209                    errorMsg = "unknown state";
210                    break;
211            }
212            PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
213            bob.setState(pbState, position, rate, SystemClock.elapsedRealtime());
214            bob.setErrorMessage(errorMsg);
215            mPlaybackState = bob.build();
216            mSession.setPlaybackState(mPlaybackState);
217            if (mListener != null) {
218                mListener.onPlayStateChanged(mPlaybackState);
219            }
220        }
221
222        @Override
223        public void onBufferingUpdate(int percent) {
224        }
225
226        @Override
227        public void onFocusLost() {
228            Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
229            long position = mRenderer == null ? -1 : mRenderer.getSeekPosition();
230            PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
231            bob.setState(PlaybackState.STATE_PAUSED, position, 0, SystemClock.elapsedRealtime());
232            bob.setErrorMessage(null);
233            mPlaybackState = bob.build();
234            mSession.setPlaybackState(mPlaybackState);
235            if (mListener != null) {
236                mListener.onPlayStateChanged(mPlaybackState);
237            }
238        }
239
240        @Override
241        public void onNextStarted() {
242        }
243
244    }
245
246    private class SessionCb extends MediaSession.Callback {
247        @Override
248        public void onPlay() {
249            mRenderer.onPlay();
250        }
251
252        @Override
253        public void onPause() {
254            mRenderer.onPause();
255        }
256    }
257}
258