2014-06-23 07:23:00 +00:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
package org.mozilla.gecko;
|
|
|
|
|
|
|
|
import org.mozilla.gecko.util.EventCallback;
|
|
|
|
import org.mozilla.gecko.util.NativeEventListener;
|
|
|
|
import org.mozilla.gecko.util.NativeJSObject;
|
|
|
|
|
|
|
|
import org.json.JSONArray;
|
|
|
|
import org.json.JSONObject;
|
|
|
|
import org.json.JSONException;
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
import android.support.v7.media.MediaControlIntent;
|
|
|
|
import android.support.v7.media.MediaRouteSelector;
|
|
|
|
import android.support.v7.media.MediaRouter;
|
|
|
|
import android.support.v7.media.MediaRouter.RouteInfo;
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
|
|
|
/* Wraper for different MediaRouter types supproted by Android. i.e. Chromecast, Miracast, etc. */
|
|
|
|
interface GeckoMediaPlayer {
|
|
|
|
public JSONObject toJSON();
|
|
|
|
public void load(String title, String url, String type, EventCallback callback);
|
|
|
|
public void play(EventCallback callback);
|
|
|
|
public void pause(EventCallback callback);
|
|
|
|
public void stop(EventCallback callback);
|
|
|
|
public void start(EventCallback callback);
|
|
|
|
public void end(EventCallback callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Manages a list of GeckoMediaPlayers methods (i.e. Chromecast/Miracast). Routes messages
|
|
|
|
* from Gecko to the correct caster based on the id of the display
|
|
|
|
*/
|
|
|
|
class MediaPlayerManager implements NativeEventListener,
|
2014-07-09 18:59:36 +00:00
|
|
|
GeckoAppShell.AppStateListener {
|
2014-06-23 07:23:00 +00:00
|
|
|
private static final String LOGTAG = "GeckoMediaPlayerManager";
|
|
|
|
|
|
|
|
private static final boolean SHOW_DEBUG = false;
|
|
|
|
// Simplified debugging interfaces
|
|
|
|
private static void debug(String msg, Exception e) {
|
|
|
|
if (SHOW_DEBUG) {
|
|
|
|
Log.e(LOGTAG, msg, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void debug(String msg) {
|
|
|
|
if (SHOW_DEBUG) {
|
|
|
|
Log.d(LOGTAG, msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private final Context context;
|
|
|
|
private final MediaRouter mediaRouter;
|
|
|
|
private final HashMap<String, GeckoMediaPlayer> displays = new HashMap<String, GeckoMediaPlayer>();
|
|
|
|
private static MediaPlayerManager instance;
|
|
|
|
|
|
|
|
public static void init(Context context) {
|
|
|
|
if (instance != null) {
|
|
|
|
debug("MediaPlayerManager initialized twice");
|
|
|
|
}
|
|
|
|
|
|
|
|
instance = new MediaPlayerManager(context);
|
|
|
|
}
|
|
|
|
|
|
|
|
private MediaPlayerManager(Context context) {
|
|
|
|
this.context = context;
|
|
|
|
|
|
|
|
if (context instanceof GeckoApp) {
|
|
|
|
GeckoApp app = (GeckoApp) context;
|
|
|
|
app.addAppStateListener(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
mediaRouter = MediaRouter.getInstance(context);
|
|
|
|
EventDispatcher.getInstance().registerGeckoThreadListener(this, "MediaPlayer:Load",
|
|
|
|
"MediaPlayer:Start",
|
|
|
|
"MediaPlayer:Stop",
|
|
|
|
"MediaPlayer:Play",
|
|
|
|
"MediaPlayer:Pause",
|
|
|
|
"MediaPlayer:Get",
|
|
|
|
"MediaPlayer:End");
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void onDestroy() {
|
|
|
|
if (instance == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
EventDispatcher.getInstance().unregisterGeckoThreadListener(instance, "MediaPlayer:Load",
|
|
|
|
"MediaPlayer:Start",
|
|
|
|
"MediaPlayer:Stop",
|
|
|
|
"MediaPlayer:Play",
|
|
|
|
"MediaPlayer:Pause",
|
|
|
|
"MediaPlayer:Get",
|
|
|
|
"MediaPlayer:End");
|
|
|
|
if (instance.context instanceof GeckoApp) {
|
|
|
|
GeckoApp app = (GeckoApp) instance.context;
|
|
|
|
app.removeAppStateListener(instance);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GeckoEventListener implementation
|
|
|
|
@Override
|
|
|
|
public void handleMessage(String event, final NativeJSObject message, final EventCallback callback) {
|
|
|
|
debug(event);
|
|
|
|
|
|
|
|
if ("MediaPlayer:Get".equals(event)) {
|
|
|
|
final JSONObject result = new JSONObject();
|
|
|
|
final JSONArray disps = new JSONArray();
|
|
|
|
for (GeckoMediaPlayer disp : displays.values()) {
|
2014-06-23 07:23:00 +00:00
|
|
|
try {
|
|
|
|
disps.put(disp.toJSON());
|
|
|
|
} catch(Exception ex) {
|
|
|
|
// This may happen if the device isn't a real Chromecast,
|
|
|
|
// for example Firefly casting devices.
|
|
|
|
Log.e(LOGTAG, "Couldn't create JSON for display", ex);
|
|
|
|
}
|
2014-06-23 07:23:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
result.put("displays", disps);
|
|
|
|
} catch(JSONException ex) {
|
|
|
|
Log.i(LOGTAG, "Error sending displays", ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
callback.sendSuccess(result);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
final GeckoMediaPlayer display = displays.get(message.getString("id"));
|
|
|
|
if (display == null) {
|
|
|
|
Log.e(LOGTAG, "Couldn't find a display for this id");
|
|
|
|
callback.sendError(null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ("MediaPlayer:Play".equals(event)) {
|
|
|
|
display.play(callback);
|
|
|
|
} else if ("MediaPlayer:Start".equals(event)) {
|
|
|
|
display.start(callback);
|
|
|
|
} else if ("MediaPlayer:Stop".equals(event)) {
|
|
|
|
display.stop(callback);
|
|
|
|
} else if ("MediaPlayer:Pause".equals(event)) {
|
|
|
|
display.pause(callback);
|
|
|
|
} else if ("MediaPlayer:End".equals(event)) {
|
|
|
|
display.end(callback);
|
|
|
|
} else if ("MediaPlayer:Load".equals(event)) {
|
|
|
|
final String url = message.optString("source", "");
|
|
|
|
final String type = message.optString("type", "video/mp4");
|
|
|
|
final String title = message.optString("title", "");
|
|
|
|
display.load(title, url, type, callback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private final MediaRouter.Callback callback = new MediaRouter.Callback() {
|
|
|
|
@Override
|
|
|
|
public void onRouteRemoved(MediaRouter router, RouteInfo route) {
|
|
|
|
debug("onRouteRemoved: route=" + route);
|
|
|
|
displays.remove(route.getId());
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("unused")
|
|
|
|
public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo route) {
|
|
|
|
}
|
|
|
|
|
|
|
|
// These methods aren't used by the support version Media Router
|
|
|
|
@SuppressWarnings("unused")
|
|
|
|
public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
|
|
|
|
debug("onRouteAdded: route=" + route);
|
|
|
|
GeckoMediaPlayer display = getMediaPlayerForRoute(route);
|
|
|
|
if (display != null) {
|
|
|
|
displays.put(route.getId(), display);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
|
|
|
|
debug("onRouteChanged: route=" + route);
|
|
|
|
GeckoMediaPlayer display = displays.get(route.getId());
|
|
|
|
if (display != null) {
|
|
|
|
displays.put(route.getId(), display);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private GeckoMediaPlayer getMediaPlayerForRoute(MediaRouter.RouteInfo route) {
|
2014-06-23 07:23:00 +00:00
|
|
|
try {
|
|
|
|
if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
|
|
|
|
return new ChromeCast(context, route);
|
|
|
|
}
|
|
|
|
} catch(Exception ex) {
|
|
|
|
debug("Error handling presentation", ex);
|
|
|
|
}
|
|
|
|
|
2014-06-23 07:23:00 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Implementing GeckoAppShell.AppStateListener */
|
|
|
|
@Override
|
|
|
|
public void onPause() {
|
|
|
|
mediaRouter.removeCallback(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onResume() {
|
|
|
|
MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder()
|
|
|
|
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
|
|
|
|
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
|
|
|
|
.build();
|
|
|
|
mediaRouter.addCallback(selectorBuilder, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onOrientationChanged() { }
|
|
|
|
|
|
|
|
}
|