Skip to content

Commit

Permalink
Bug 1064669 - Use a Viewless Fragment for managing MediaPlayer lifecy…
Browse files Browse the repository at this point in the history
…cle. r=mfinkle
  • Loading branch information
Wes Johnston committed Sep 19, 2014
1 parent 34f30cd commit 9fff609
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 170 deletions.
46 changes: 26 additions & 20 deletions mobile/android/base/BrowserApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.Override;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.EnumSet;
Expand All @@ -16,6 +17,7 @@
import java.util.Locale;
import java.util.Vector;

import android.support.v4.app.Fragment;
import org.json.JSONException;
import org.json.JSONObject;

Expand Down Expand Up @@ -651,16 +653,6 @@ public void onEnabledChanged(boolean enabled) {
// Set the maximum bits-per-pixel the favicon system cares about.
IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth());

Class<?> mediaManagerClass = getMediaPlayerManager();
if (mediaManagerClass != null) {
try {
Method init = mediaManagerClass.getMethod("init", Context.class);
init.invoke(null, this);
} catch(Exception ex) {
Log.e(LOGTAG, "Error initializing media manager", ex);
}
}

mTilesRecorder = new TilesRecorder();
}

Expand Down Expand Up @@ -1163,16 +1155,6 @@ public void onDestroy() {
}
}

Class<?> mediaManagerClass = getMediaPlayerManager();
if (mediaManagerClass != null) {
try {
Method destroy = mediaManagerClass.getMethod("onDestroy", (Class[]) null);
destroy.invoke(null);
} catch(Exception ex) {
Log.e(LOGTAG, "Error destroying media manager", ex);
}
}

super.onDestroy();
}

Expand Down Expand Up @@ -1599,6 +1581,29 @@ public void run() {
}
});

if (AppConstants.MOZ_MEDIA_PLAYER) {
// Check if the fragment is already added. This should never be true here, but this is
// a nice safety check.
// If casting is disabled, these classes aren't built. We use reflection to initialize them.
final Class<?> mediaManagerClass = getMediaPlayerManager();

if (mediaManagerClass != null) {
try {
final String tag = "";
mediaManagerClass.getDeclaredField("MEDIA_PLAYER_TAG").get(tag);
Log.i(LOGTAG, "Found tag " + tag);
final Fragment frag = getSupportFragmentManager().findFragmentByTag(tag);
if (frag == null) {
final Method getInstance = mediaManagerClass.getMethod("newInstance", (Class[]) null);
final Fragment mpm = (Fragment) getInstance.invoke(null);
getSupportFragmentManager().beginTransaction().disallowAddToBackStack().add(mpm, tag).commit();
}
} catch (Exception ex) {
Log.e(LOGTAG, "Error initializing media manager", ex);
}
}
}

if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) {
// Start (this acts as ping if started already) the stumbler lib; if the stumbler has queued data it will upload it.
// Stumbler operates on its own thread, and startup impact is further minimized by delaying work (such as upload) a few seconds.
Expand All @@ -1611,6 +1616,7 @@ public void run() {
}
}, oneSecondInMillis);
}

super.handleMessage(event, message);
} else if (event.equals("Gecko:Ready")) {
// Handle this message in GeckoApp, but also enable the Settings
Expand Down
2 changes: 1 addition & 1 deletion mobile/android/base/ChromeCast.java
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public void onResult(MediaChannelResult result) {
public ChromeCast(Context context, RouteInfo route) {
int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
if (status != ConnectionResult.SUCCESS) {
throw new IllegalStateException("Play services are required for Chromecast support (go status code " + status + ")");
throw new IllegalStateException("Play services are required for Chromecast support (got status code " + status + ")");
}

this.context = context;
Expand Down
6 changes: 3 additions & 3 deletions mobile/android/base/GeckoApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -1914,7 +1914,7 @@ public void onResume()
}

if (mAppStateListeners != null) {
for (GeckoAppShell.AppStateListener listener: mAppStateListeners) {
for (GeckoAppShell.AppStateListener listener : mAppStateListeners) {
listener.onResume();
}
}
Expand Down Expand Up @@ -1946,7 +1946,7 @@ public void run() {
Log.w(LOGTAG, "Can't record session: rec is null.");
}
}
});
});
}

@Override
Expand Down Expand Up @@ -1996,7 +1996,7 @@ public void run() {
});

if (mAppStateListeners != null) {
for(GeckoAppShell.AppStateListener listener: mAppStateListeners) {
for (GeckoAppShell.AppStateListener listener : mAppStateListeners) {
listener.onPause();
}
}
Expand Down
155 changes: 59 additions & 96 deletions mobile/android/base/MediaPlayerManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,44 @@

package org.mozilla.gecko;

import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.mozglue.JNITarget;
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.os.Bundle;
import android.support.v4.app.Fragment;
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 com.google.android.gms.cast.CastMediaControlIntent;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.mozglue.JNITarget;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;

import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Map;

/* 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,
GeckoAppShell.AppStateListener {
public class MediaPlayerManager extends Fragment implements NativeEventListener {
/**
* Create a new instance of DetailsFragment, initialized to
* show the text at 'index'.
*/
@JNITarget
public static MediaPlayerManager newInstance() {
return new MediaPlayerManager();
}

private static final String LOGTAG = "GeckoMediaPlayerManager";

@JNITarget
public static final String MEDIA_PLAYER_TAG = "MPManagerFragment";

private static final boolean SHOW_DEBUG = false;
// Simplified debugging interfaces
private static void debug(String msg, Exception e) {
Expand All @@ -48,100 +57,43 @@ private static void debug(String msg) {
}
}

private final Context context;
private final MediaRouter mediaRouter;
private MediaRouter mediaRouter = null;
private final Map<String, GeckoMediaPlayer> displays = new HashMap<String, GeckoMediaPlayer>();
private static MediaPlayerManager instance;

@JNITarget
public static void init(Context context) {
if (instance != null) {
debug("MediaPlayerManager initialized twice");
return;
}

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);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EventDispatcher.getInstance().registerGeckoThreadListener(this,
"MediaPlayer:Load",
"MediaPlayer:Start",
"MediaPlayer:Stop",
"MediaPlayer:Play",
"MediaPlayer:Pause",
"MediaPlayer:Get",
"MediaPlayer:End",
"MediaPlayer:Mirror",
"MediaPlayer:Message");
"MediaPlayer:Load",
"MediaPlayer:Start",
"MediaPlayer:Stop",
"MediaPlayer:Play",
"MediaPlayer:Pause",
"MediaPlayer:End",
"MediaPlayer:Mirror",
"MediaPlayer:Message");
}

@Override
@JNITarget
public static void onDestroy() {
if (instance == null) {
return;
}

EventDispatcher.getInstance().unregisterGeckoThreadListener(instance,
public void onDestroy() {
super.onDestroy();
EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
"MediaPlayer:Load",
"MediaPlayer:Start",
"MediaPlayer:Stop",
"MediaPlayer:Play",
"MediaPlayer:Pause",
"MediaPlayer:Get",
"MediaPlayer:End",
"MediaPlayer:Mirror",
"MediaPlayer:Message");
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();

final Iterator<GeckoMediaPlayer> items = displays.values().iterator();
while (items.hasNext()) {
GeckoMediaPlayer disp = items.next();
try {
JSONObject json = disp.toJSON();
if (json == null) {
items.remove();
} else {
disps.put(json);
}
} catch(Exception ex) {
// This may happen if the device isn't a real Chromecast,
// for example Matchstick casting devices.
Log.e(LOGTAG, "Couldn't create JSON for display", ex);
}
}

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: " + message.getString("id") + " for message: " + event);
Expand Down Expand Up @@ -179,6 +131,8 @@ public void handleMessage(String event, final NativeJSObject message, final Even
public void onRouteRemoved(MediaRouter router, RouteInfo route) {
debug("onRouteRemoved: route=" + route);
displays.remove(route.getId());
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
"MediaPlayer:Removed", route.getId()));
}

@SuppressWarnings("unused")
Expand All @@ -201,26 +155,30 @@ public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
@Override
public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
debug("onRouteAdded: route=" + route);
GeckoMediaPlayer display = getMediaPlayerForRoute(route);
final GeckoMediaPlayer display = getMediaPlayerForRoute(route);
if (display != null) {
displays.put(route.getId(), display);
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
"MediaPlayer:Added", display.toJSON().toString()));
}
}

@Override
public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
debug("onRouteChanged: route=" + route);
GeckoMediaPlayer display = displays.get(route.getId());
final GeckoMediaPlayer display = displays.get(route.getId());
if (display != null) {
displays.put(route.getId(), display);
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
"MediaPlayer:Changed", display.toJSON().toString()));
}
}
};

private GeckoMediaPlayer getMediaPlayerForRoute(MediaRouter.RouteInfo route) {
try {
if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
return new ChromeCast(context, route);
return new ChromeCast(getActivity(), route);
}
} catch(Exception ex) {
debug("Error handling presentation", ex);
Expand All @@ -229,23 +187,28 @@ private GeckoMediaPlayer getMediaPlayerForRoute(MediaRouter.RouteInfo route) {
return null;
}

/* Implementing GeckoAppShell.AppStateListener */
@Override
public void onPause() {
super.onPause();
mediaRouter.removeCallback(callback);
mediaRouter = null;
}

@Override
public void onResume() {
MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder()
super.onResume();

// The mediaRouter shouldn't exist here, but this is a nice safety check.
if (mediaRouter != null) {
return;
}

mediaRouter = MediaRouter.getInstance(getActivity());
final MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.addControlCategory(CastMediaControlIntent.categoryForCast(ChromeCast.MIRROR_RECEIVER_APP_ID))
.build();
mediaRouter.addCallback(selectorBuilder, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
}

@Override
public void onOrientationChanged() { }

}
Loading

0 comments on commit 9fff609

Please sign in to comment.