Bug 1046537 - Better error handling from chromecast code. r=mfinkle a=sylvestre
authorWes Johnston <wjohnston@mozilla.com>
Fri, 22 Aug 2014 11:20:02 -0700
changeset 217638 6c4d96765e745cbb3bf390542c016b3037792265
parent 217637 b0e71a84eb05a38e2b249dce1c065f540157b734
child 217639 9251ac84b3e4d4c59874cf4226a9192b112cda98
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle, sylvestre
bugs1046537
milestone33.0a2
Bug 1046537 - Better error handling from chromecast code. r=mfinkle a=sylvestre
mobile/android/base/ChromeCast.java
mobile/android/chrome/content/CastingApps.js
mobile/android/modules/MediaPlayerApp.jsm
--- a/mobile/android/base/ChromeCast.java
+++ b/mobile/android/base/ChromeCast.java
@@ -29,21 +29,25 @@ import com.google.android.gms.common.Goo
 import android.content.Context;
 import android.os.Bundle;
 import android.support.v7.media.MediaRouter.RouteInfo;
 import android.util.Log;
 
 /* Implementation of GeckoMediaPlayer for talking to ChromeCast devices */
 class ChromeCast implements GeckoMediaPlayer {
     private static final boolean SHOW_DEBUG = false;
+    private static final String LOGTAG = "GeckoChromeCast";
 
     private final Context context;
     private final RouteInfo route;
     private GoogleApiClient apiClient;
     private RemoteMediaPlayer remoteMediaPlayer;
+    private String mSessionId;
+    private MirrorChannel mMirrorChannel;
+    private boolean mApplicationStarted = false;
 
     // Callback to start playback of a url on a remote device
     private class VideoPlayCallback implements ResultCallback<ApplicationConnectionResult>,
                                                RemoteMediaPlayer.OnStatusUpdatedListener,
                                                RemoteMediaPlayer.OnMetadataUpdatedListener {
         private final String url;
         private final String type;
         private final String title;
@@ -80,26 +84,27 @@ class ChromeCast implements GeckoMediaPl
         @Override
         public void onResult(ApplicationConnectionResult result) {
             Status status = result.getStatus();
             debug("ApplicationConnectionResultCallback.onResult: statusCode" + status.getStatusCode());
             if (status.isSuccess()) {
                 remoteMediaPlayer = new RemoteMediaPlayer();
                 remoteMediaPlayer.setOnStatusUpdatedListener(this);
                 remoteMediaPlayer.setOnMetadataUpdatedListener(this);
+                mSessionId = result.getSessionId();
 
                 try {
                     Cast.CastApi.setMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace(), remoteMediaPlayer);
                 } catch (IOException e) {
                     debug("Exception while creating media channel", e);
                 }
 
                 startPlayback();
             } else {
-                callback.sendError(null);
+                callback.sendError(status.toString());
             }
         }
 
         private void startPlayback() {
             MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
             mediaMetadata.putString(MediaMetadata.KEY_TITLE, title);
             MediaInfo mediaInfo = new MediaInfo.Builder(url)
                                                .setContentType(type)
@@ -112,28 +117,28 @@ class ChromeCast implements GeckoMediaPl
                     public void onResult(MediaChannelResult result) {
                         if (result.getStatus().isSuccess()) {
                             callback.sendSuccess(null);
                             debug("Media loaded successfully");
                             return;
                         }
 
                         debug("Media load failed " + result.getStatus());
-                        callback.sendError(null);
+                        callback.sendError(result.getStatus().toString());
                     }
                 });
 
                 return;
             } catch (IllegalStateException e) {
                 debug("Problem occurred with media during loading", e);
             } catch (Exception e) {
                 debug("Problem opening media during loading", e);
             }
 
-            callback.sendError(null);
+            callback.sendError("");
         }
     }
 
     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 + ")");
         }
@@ -176,16 +181,18 @@ class ChromeCast implements GeckoMediaPl
         });
 
         apiClient = new GoogleApiClient.Builder(context)
             .addApi(Cast.API, apiOptionsBuilder.build())
             .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                 @Override
                 public void onConnected(Bundle connectionHint) {
                     if (!apiClient.isConnected()) {
+                        debug("Connection failed");
+                        callback.sendError("Not connected");
                         return;
                     }
 
                     // Launch the media player app and launch this url once its loaded
                     try {
                         Cast.CastApi.launchApplication(apiClient, CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID, true)
                                     .setResultCallback(new VideoPlayCallback(url, type, title, callback));
                     } catch (Exception e) {
@@ -207,80 +214,107 @@ class ChromeCast implements GeckoMediaPl
         callback.sendSuccess(null);
     }
 
     public void stop(final EventCallback callback) {
         // Nothing to be done here
         callback.sendSuccess(null);
     }
 
+    public boolean verifySession(final EventCallback callback) {
+        if (apiClient == null || !apiClient.isConnected()) {
+            debug("Can't play. No connection");
+            callback.sendError("Not connected");
+            return false;
+        }
+
+        if (mSessionId == null) {
+            debug("Can't play. No session");
+            callback.sendError("No session");
+            return false;
+        }
+
+        return true;
+    }
+
     public void play(final EventCallback callback) {
+        if (!verifySession(callback)) {
+            return;
+        }
+
         remoteMediaPlayer.play(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
             @Override
             public void onResult(MediaChannelResult result) {
                 Status status = result.getStatus();
                 if (!status.isSuccess()) {
-                    debug("Unable to toggle pause: " + status.getStatusCode());
-                    callback.sendError(null);
+                    debug("Unable to play: " + status.getStatusCode());
+                    callback.sendError(status.toString());
                 } else {
                     callback.sendSuccess(null);
                 }
             }
         });
     }
 
     public void pause(final EventCallback callback) {
+        if (!verifySession(callback)) {
+            return;
+        }
+
         remoteMediaPlayer.pause(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
             @Override
             public void onResult(MediaChannelResult result) {
                 Status status = result.getStatus();
                 if (!status.isSuccess()) {
-                    debug("Unable to toggle pause: " + status.getStatusCode());
-                    callback.sendError(null);
+                    debug("Unable to pause: " + status.getStatusCode());
+                    callback.sendError(status.toString());
                 } else {
                     callback.sendSuccess(null);
                 }
             }
         });
     }
 
     public void end(final EventCallback callback) {
+        if (!verifySession(callback)) {
+            return;
+        }
+
         Cast.CastApi.stopApplication(apiClient).setResultCallback(new ResultCallback<Status>() {
             @Override
             public void onResult(Status result) {
                 if (result.isSuccess()) {
                     try {
                         Cast.CastApi.removeMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace());
                         remoteMediaPlayer = null;
+                        mSessionId = null;
                         apiClient.disconnect();
                         apiClient = null;
 
                         if (callback != null) {
                             callback.sendSuccess(null);
                         }
 
                         return;
                     } catch(Exception ex) {
                         debug("Error ending", ex);
                     }
                 }
 
                 if (callback != null) {
-                    callback.sendError(null);
+                    callback.sendError(result.getStatus().toString());
                 }
             }
         });
     }
 
-    private static final String LOGTAG = "GeckoChromeCast";
     private void debug(String msg, Exception e) {
         if (SHOW_DEBUG) {
             Log.e(LOGTAG, msg, e);
         }
     }
 
     private void debug(String msg) {
         if (SHOW_DEBUG) {
             Log.d(LOGTAG, msg);
         }
     }
-
 }
--- a/mobile/android/chrome/content/CastingApps.js
+++ b/mobile/android/chrome/content/CastingApps.js
@@ -457,18 +457,21 @@ var CastingApps = {
   },
 
   closeExternal: function() {
     if (!this.session) {
       return;
     }
 
     this.session.remoteMedia.shutdown();
+    this._shutdown();
+  },
+
+  _shutdown: function() {
     this.session.app.stop();
-
     let video = this.session.videoRef.get();
     if (video) {
       this._sendEventToVideo(video, { active: false });
       this._updatePageAction();
     }
 
     delete this.session;
   },
@@ -486,16 +489,17 @@ var CastingApps = {
     if (video) {
       this._sendEventToVideo(video, { active: true });
       this._updatePageAction(video);
     }
   },
 
   onRemoteMediaStop: function(aRemoteMedia) {
     sendMessageToJava({ type: "Casting:Stopped" });
+    this._shutdown();
   },
 
   onRemoteMediaStatus: function(aRemoteMedia) {
     if (!this.session) {
       return;
     }
 
     let status = aRemoteMedia.status;
--- a/mobile/android/modules/MediaPlayerApp.jsm
+++ b/mobile/android/modules/MediaPlayerApp.jsm
@@ -31,24 +31,28 @@ function send(type, data, callback) {
 function MediaPlayerApp(service) {
   this.service = service;
   this.location = service.location;
   this.id = service.uuid;
 }
 
 MediaPlayerApp.prototype = {
   start: function start(callback) {
-    send("MediaPlayer:Start", { id: this.id }, (result) => {
-      if (callback) callback(true);
+    send("MediaPlayer:Start", { id: this.id }, (result, err) => {
+      if (callback) {
+        callback(err == null);
+      }
     });
   },
 
   stop: function stop(callback) {
-    send("MediaPlayer:Stop", { id: this.id }, (result) => {
-      if (callback) callback(true);
+    send("MediaPlayer:Stop", { id: this.id }, (result, err) => {
+      if (callback) {
+        callback(err == null);
+      }
     });
   },
 
   remoteMedia: function remoteMedia(callback, listener) {
     if (callback) {
       callback(new RemoteMedia(this.id, listener));
     }
   },
@@ -64,38 +68,56 @@ function RemoteMedia(id, listener) {
     Services.tm.mainThread.dispatch((function() {
       this._listener.onRemoteMediaStart(this);
     }).bind(this), Ci.nsIThread.DISPATCH_NORMAL);
   }
 }
 
 RemoteMedia.prototype = {
   shutdown: function shutdown() {
-    this._send("MediaPlayer:End", {}, (result) => {
+    this._send("MediaPlayer:End", {}, (result, err) => {
       this._status = "shutdown";
       if ("onRemoteMediaStop" in this._listener) {
         this._listener.onRemoteMediaStop(this);
       }
     });
   },
 
   play: function play() {
-    this._send("MediaPlayer:Play", {}, (result) => {
+    this._send("MediaPlayer:Play", {}, (result, err) => {
+      if (err) {
+        Cu.reportError("Can't play " + err);
+        this.shutdown();
+        return;
+      }
+
       this._status = "started";
     });
   },
 
   pause: function pause() {
-    this._send("MediaPlayer:Pause", {}, (result) => {
+    this._send("MediaPlayer:Pause", {}, (result, err) => {
+      if (err) {
+        Cu.reportError("Can't pause " + err);
+        this.shutdown();
+        return;
+      }
+
       this._status = "paused";
     });
   },
 
   load: function load(aData) {
-    this._send("MediaPlayer:Load", aData, (result) => {
+    this._send("MediaPlayer:Load", aData, (result, err) => {
+      if (err) {
+        Cu.reportError("Can't load " + err);
+        this.shutdown();
+        return;
+      }
+
       this._status = "started";
     })
   },
 
   get status() {
     return this._status;
   },