mobile/android/modules/RokuApp.jsm

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     4  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 "use strict";
     8 this.EXPORTED_SYMBOLS = ["RokuApp"];
    10 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
    12 Cu.import("resource://gre/modules/Services.jsm");
    14 function log(msg) {
    15   //Services.console.logStringMessage(msg);
    16 }
    18 const PROTOCOL_VERSION = 1;
    20 /* RokuApp is a wrapper for interacting with a Roku channel.
    21  * The basic interactions all use a REST API.
    22  * spec: http://sdkdocs.roku.com/display/sdkdoc/External+Control+Guide
    23  */
    24 function RokuApp(service, app) {
    25   this.service = service;
    26   this.resourceURL = this.service.location;
    27   this.app = app;
    28   this.appID = -1;
    29 }
    31 RokuApp.prototype = {
    32   status: function status(callback) {
    33     // We have no way to know if the app is running, so just return "unknown"
    34     // but we use this call to fetch the appID for the given app name
    35     let url = this.resourceURL + "query/apps";
    36     let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
    37     xhr.open("GET", url, true);
    38     xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
    39     xhr.overrideMimeType("text/xml");
    41     xhr.addEventListener("load", (function() {
    42       if (xhr.status == 200) {
    43         let doc = xhr.responseXML;
    44         let apps = doc.querySelectorAll("app");
    45         for (let app of apps) {
    46           if (app.textContent == this.app) {
    47             this.appID = app.id;
    48           }
    49         }
    50       }
    52       // Since ECP has no way of telling us if an app is running, we always return "unknown"
    53       if (callback) {
    54         callback({ state: "unknown" });
    55       }
    56     }).bind(this), false);
    58     xhr.addEventListener("error", (function() {
    59       if (callback) {
    60         callback({ state: "unknown" });
    61       }
    62     }).bind(this), false);
    64     xhr.send(null);
    65   },
    67   start: function start(callback) {
    68     // We need to make sure we have cached the appID
    69     if (this.appID == -1) {
    70       this.status(function() {
    71         // If we found the appID, use it to make a new start call
    72         if (this.appID != -1) {
    73           this.start(callback);
    74         } else {
    75           // We failed to start the app, so let the caller know
    76           callback(false);
    77         }
    78       }.bind(this));
    79       return;
    80     }
    82     // Start a given app with any extra query data. Each app uses it's own data scheme.
    83     // NOTE: Roku will also pass "source=external-control" as a param
    84     let url = this.resourceURL + "launch/" + this.appID + "?version=" + parseInt(PROTOCOL_VERSION);
    85     let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
    86     xhr.open("POST", url, true);
    87     xhr.overrideMimeType("text/plain");
    89     xhr.addEventListener("load", (function() {
    90       if (callback) {
    91         callback(xhr.status === 200);
    92       }
    93     }).bind(this), false);
    95     xhr.addEventListener("error", (function() {
    96       if (callback) {
    97         callback(false);
    98       }
    99     }).bind(this), false);
   101     xhr.send(null);
   102   },
   104   stop: function stop(callback) {
   105     // Roku doesn't seem to support stopping an app, so let's just go back to
   106     // the Home screen
   107     let url = this.resourceURL + "keypress/Home";
   108     let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
   109     xhr.open("POST", url, true);
   110     xhr.overrideMimeType("text/plain");
   112     xhr.addEventListener("load", (function() {
   113       if (callback) {
   114         callback(xhr.status === 200);
   115       }
   116     }).bind(this), false);
   118     xhr.addEventListener("error", (function() {
   119       if (callback) {
   120         callback(false);
   121       }
   122     }).bind(this), false);
   124     xhr.send(null);
   125   },
   127   remoteMedia: function remoteMedia(callback, listener) {
   128     if (this.appID != -1) {
   129       if (callback) {
   130         callback(new RemoteMedia(this.resourceURL, listener));
   131       }
   132     } else {
   133       if (callback) {
   134         callback();
   135       }
   136     }
   137   }
   138 }
   140 /* RemoteMedia provides a wrapper for using TCP socket to control Roku apps.
   141  * The server implementation must be built into the Roku receiver app.
   142  */
   143 function RemoteMedia(url, listener) {
   144   this._url = url;
   145   this._listener = listener;
   146   this._status = "uninitialized";
   148   let serverURI = Services.io.newURI(this._url , null, null);
   149   this._socket = Cc["@mozilla.org/network/socket-transport-service;1"].getService(Ci.nsISocketTransportService).createTransport(null, 0, serverURI.host, 9191, null);
   150   this._outputStream = this._socket.openOutputStream(0, 0, 0);
   152   this._scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
   154   this._inputStream = this._socket.openInputStream(0, 0, 0);
   155   this._pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(Ci.nsIInputStreamPump);
   156   this._pump.init(this._inputStream, -1, -1, 0, 0, true);
   157   this._pump.asyncRead(this, null);
   158 }
   160 RemoteMedia.prototype = {
   161   onStartRequest: function(request, context) {
   162   },
   164   onDataAvailable: function(request, context, stream, offset, count) {
   165     this._scriptableStream.init(stream);
   166     let data = this._scriptableStream.read(count);
   167     if (!data) {
   168       return;
   169     }
   171     let msg = JSON.parse(data);
   172     if (this._status === msg._s) {
   173       return;
   174     }
   176     this._status = msg._s;
   178     if (this._listener) {
   179       // Check to see if we are getting the initial "connected" message
   180       if (this._status == "connected" && "onRemoteMediaStart" in this._listener) {
   181         this._listener.onRemoteMediaStart(this);
   182       }
   184       if ("onRemoteMediaStatus" in this._listener) {
   185         this._listener.onRemoteMediaStatus(this);
   186       }
   187     }
   188   },
   190   onStopRequest: function(request, context, result) {
   191     if (this._listener && "onRemoteMediaStop" in this._listener)
   192       this._listener.onRemoteMediaStop(this);
   193   },
   195   _sendMsg: function _sendMsg(data) {
   196     if (!data)
   197       return;
   199     // Add the protocol version
   200     data["_v"] = PROTOCOL_VERSION;
   202     let raw = JSON.stringify(data);
   203     this._outputStream.write(raw, raw.length);
   204   },
   206   shutdown: function shutdown() {
   207     this._outputStream.close();
   208     this._inputStream.close();
   209   },
   211   get active() {
   212     return (this._socket && this._socket.isAlive());
   213   },
   215   play: function play() {
   216     // TODO: add position support
   217     this._sendMsg({ type: "PLAY" });
   218   },
   220   pause: function pause() {
   221     this._sendMsg({ type: "STOP" });
   222   },
   224   load: function load(aData) {
   225     this._sendMsg({ type: "LOAD", title: aData.title, source: aData.source, poster: aData.poster });
   226   },
   228   get status() {
   229     return this._status;
   230   }
   231 }

mercurial