/*
* Copyright (C) 2005-2009 Team XBMC
* http://xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBMC Remote; see the file license. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
package org.xbmc.android.remote.business;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.HashSet;
import org.xbmc.android.util.ClientFactory;
import org.xbmc.android.util.HostFactory;
import org.xbmc.api.business.DataResponse;
import org.xbmc.api.business.INotifiableManager;
import org.xbmc.api.data.IControlClient;
import org.xbmc.api.data.IControlClient.ICurrentlyPlaying;
import org.xbmc.api.data.IInfoClient;
import org.xbmc.api.info.PlayStatus;
import org.xbmc.api.object.Host;
import org.xbmc.jsonrpc.Connection;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* Activities (and other stuff) can subscribe to this thread in order to obtain
* real-time "Now playing" information. The thread will send relevant messages
* to all subscribers. If there are no subscriptions, nothing is polled.
*
* Please remember to unsubscribe (e.g. onPause()) in order to avoid unnecessary
* polling.
*
* @author Team XBMC
*/
public class NowPlayingPollerThread extends Thread {
private static final String TAG = "NowPlayingPollerThread";
public static final String BUNDLE_CURRENTLY_PLAYING = "CurrentlyPlaying";
public static final String BUNDLE_LAST_PLAYLIST = "LastPlaylist";
public static final String BUNDLE_LAST_PLAYPOSITION = "LastPlayPosition";
public static final int MESSAGE_CONNECTION_ERROR = 1;
public static final int MESSAGE_RECONFIGURE = 2;
public static final int MESSAGE_PROGRESS_CHANGED = 666;
public static final int MESSAGE_PLAYLIST_ITEM_CHANGED = 667;
public static final int MESSAGE_COVER_CHANGED = 668;
public static final int MESSAGE_PLAYSTATE_CHANGED = 669;
private IInfoClient mInfo;
private IControlClient mControl;
private final HashSet<Handler> mSubscribers;
private String mCoverPath;
private Bitmap mCover;
private int mPlayList = -1;
private int mPosition = -1;
/**
* Since this one is kinda of its own, we use a stub as manager.
* @TODO create some toats or at least logs instead of empty on* methods.
*/
private final INotifiableManager mManagerStub;
public NowPlayingPollerThread(final Context context){
mManagerStub = new INotifiableManager() {
public void onMessage(int code, String message) { }
public void onMessage(String message) { }
public void onError(Exception e) {
// XXX link to context will eventually change if activity which created the thread changes (java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare())
//Toast toast = Toast.makeText(context, "Poller Error: " + e.getMessage(), Toast.LENGTH_LONG);
//toast.show();
if (e.getMessage() != null) {
Log.e(TAG, e.getMessage());
}
e.printStackTrace();
}
public void onFinish(DataResponse<?> response) {
}
public void onWrongConnectionState(int state, Command<?> cmd) {
}
public void retryAll() {
}
};
try {
mControl = ClientFactory.getControlClient(mManagerStub, context);
} catch (Exception e2) {
mControl = null;
}
try {
mInfo = ClientFactory.getInfoClient(mManagerStub, context);
} catch (Exception e1) {
mInfo = null;
}
mSubscribers = new HashSet<Handler>();
}
public void subscribe(Handler handler) {
// update handler on the state of affairs
final ICurrentlyPlaying currPlaying = mControl.getCurrentlyPlaying(mManagerStub);
sendSingleMessage(handler, MESSAGE_PROGRESS_CHANGED, currPlaying);
sendSingleMessage(handler, MESSAGE_PLAYLIST_ITEM_CHANGED, currPlaying);
sendSingleMessage(handler, MESSAGE_COVER_CHANGED, currPlaying);
synchronized (mSubscribers) {
mSubscribers.add(handler);
}
}
public void unSubscribe(Handler handler){
synchronized (mSubscribers) {
mSubscribers.remove(handler);
}
}
public Bitmap getNowPlayingCover(){
return mCover;
}
/**
* @return True if the stored cover art was updated.
*/
private boolean updateNowPlayingCover() {
try {
String downloadURI = mInfo.getCurrentlyPlayingThumbURI(mManagerStub);
if (downloadURI == null || downloadURI.length() == 0) {
mCover = null;
String oldCoverPath = mCoverPath;
mCoverPath = null;
// If we had previously been handing out a thumbnail, clients
// need to update to the lack of one.
return oldCoverPath != null;
}
if (!downloadURI.equals(mCoverPath)) {
mCoverPath = downloadURI;
byte[] buffer = download(downloadURI);
if (buffer == null || buffer.length == 0)
mCover = null;
else
mCover = BitmapFactory.decodeByteArray(buffer, 0, buffer.length);
return true;
}
} catch (MalformedURLException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (URISyntaxException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
return false;
}
public void sendMessage(int what, ICurrentlyPlaying curr) {
synchronized (mSubscribers) {
for (Handler handler : mSubscribers) {
sendSingleMessage(handler, what, curr);
}
}
}
private void sendSingleMessage(Handler handler, int what, ICurrentlyPlaying curr) {
Message msg = Message.obtain(handler);
msg.what = what;
Bundle bundle = msg.getData();
bundle.putSerializable(BUNDLE_CURRENTLY_PLAYING, curr);
bundle.putInt(BUNDLE_LAST_PLAYLIST, mPlayList);
bundle.putInt(BUNDLE_LAST_PLAYPOSITION, mPosition);
msg.setTarget(handler);
handler.sendMessage(msg);
}
public void sendEmptyMessage(int what) {
synchronized (mSubscribers) {
for (Handler handler : mSubscribers) {
handler.sendEmptyMessage(what);
}
}
}
public void run() {
String lastPos = "-1";
int lastPlayStatus = PlayStatus.UNKNOWN;
int currentPlayStatus = PlayStatus.UNKNOWN;
int currentMediaType = 0;
IControlClient control = mControl; // use local reference for faster access
HashSet<Handler> subscribers;
while (!isInterrupted() ) {
synchronized (mSubscribers) {
subscribers = new HashSet<Handler>(mSubscribers);
}
if (subscribers.size() > 0){
/* if (!control.isConnected()) {
sendEmptyMessage(MESSAGE_CONNECTION_ERROR);
} else {*/
ICurrentlyPlaying currPlaying;
try{
currPlaying = control.getCurrentlyPlaying(mManagerStub);
} catch(Exception e) {
e.printStackTrace();
sendEmptyMessage(MESSAGE_CONNECTION_ERROR);
return;
}
currentPlayStatus = currPlaying.getPlayStatus();
String currentPos = currPlaying.getTitle() + currPlaying.getDuration();
// send changed status
if (currentPlayStatus == PlayStatus.PLAYING) {
sendMessage(MESSAGE_PROGRESS_CHANGED, currPlaying);
boolean coverChanged = updateNowPlayingCover();
if (coverChanged) {
sendMessage(MESSAGE_COVER_CHANGED, currPlaying);
}
}
// play state changed?
if ((currentPlayStatus != lastPlayStatus) || (currentMediaType != currPlaying.getMediaType())) {
currentMediaType = currPlaying.getMediaType();
if (currentPlayStatus == PlayStatus.PLAYING) {
mPlayList = control.getPlaylistId(mManagerStub);
sendMessage(MESSAGE_PLAYSTATE_CHANGED, currPlaying);
} else {
sendMessage(MESSAGE_PLAYSTATE_CHANGED, currPlaying);
sendMessage(MESSAGE_PROGRESS_CHANGED, currPlaying);
}
boolean coverChanged = updateNowPlayingCover();
if (coverChanged) {
sendMessage(MESSAGE_COVER_CHANGED, currPlaying);
}
}
// play position changed?
if (!lastPos.equals(currentPos)) {
lastPos = currentPos;
if (currPlaying.getPlaylistPosition() >= 0) {
mPosition = currPlaying.getPlaylistPosition();
}
sendMessage(MESSAGE_PLAYLIST_ITEM_CHANGED, currPlaying);
boolean coverChanged = updateNowPlayingCover();
if (coverChanged) {
sendMessage(MESSAGE_COVER_CHANGED, currPlaying);
}
}
}
else{
this.interrupt();
}
try {
sleep(1000);
} catch (InterruptedException e) {
sendEmptyMessage(MESSAGE_RECONFIGURE);
return;
}
lastPlayStatus = currentPlayStatus;
}
}
private byte[] download(String pathToDownload) throws IOException, URISyntaxException {
Connection connection;
final Host host = HostFactory.host;
if (host != null) {
connection = Connection.getInstance(host.addr, host.port);
connection.setAuth(host.user, host.pass);
} else {
connection = Connection.getInstance(null, 0);
}
return connection.download(pathToDownload);
}
}