mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
bug 1037015 - support tab casting to chromecast r=mfinkle
This commit is contained in:
parent
f7bea96e82
commit
828e42e81c
@ -1,4 +1,5 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
@ -10,6 +11,8 @@ import org.mozilla.gecko.util.EventCallback;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
|
||||
import com.google.android.gms.cast.Cast.MessageReceivedCallback;
|
||||
import com.google.android.gms.cast.ApplicationMetadata;
|
||||
import com.google.android.gms.cast.Cast;
|
||||
import com.google.android.gms.cast.Cast.ApplicationConnectionResult;
|
||||
import com.google.android.gms.cast.CastDevice;
|
||||
@ -33,10 +36,13 @@ import android.util.Log;
|
||||
class ChromeCast implements GeckoMediaPlayer {
|
||||
private static final boolean SHOW_DEBUG = false;
|
||||
|
||||
static final String MIRROR_RECIEVER_APP_ID = "D40D28D6";
|
||||
|
||||
private final Context context;
|
||||
private final RouteInfo route;
|
||||
private GoogleApiClient apiClient;
|
||||
private RemoteMediaPlayer remoteMediaPlayer;
|
||||
private boolean canMirror;
|
||||
|
||||
// Callback to start playback of a url on a remote device
|
||||
private class VideoPlayCallback implements ResultCallback<ApplicationConnectionResult>,
|
||||
@ -133,6 +139,7 @@ class ChromeCast implements GeckoMediaPlayer {
|
||||
public ChromeCast(Context context, RouteInfo route) {
|
||||
this.context = context;
|
||||
this.route = route;
|
||||
this.canMirror = route.supportsControlCategory(CastMediaControlIntent.categoryForCast(MIRROR_RECIEVER_APP_ID));
|
||||
}
|
||||
|
||||
// This dumps everything we can find about the device into JSON. This will hopefully make it
|
||||
@ -146,6 +153,7 @@ class ChromeCast implements GeckoMediaPlayer {
|
||||
obj.put("friendlyName", device.getFriendlyName());
|
||||
obj.put("location", device.getIpAddress().toString());
|
||||
obj.put("modelName", device.getModelName());
|
||||
obj.put("mirror", canMirror);
|
||||
// For now we just assume all of these are Google devices
|
||||
obj.put("manufacturer", "Google Inc.");
|
||||
} catch(JSONException ex) {
|
||||
@ -263,6 +271,127 @@ class ChromeCast implements GeckoMediaPlayer {
|
||||
});
|
||||
}
|
||||
|
||||
private String mSessionId;
|
||||
MirrorChannel mMirrorChannel;
|
||||
boolean mApplicationStarted = false;
|
||||
|
||||
class MirrorChannel implements MessageReceivedCallback {
|
||||
|
||||
/**
|
||||
* @return custom namespace
|
||||
*/
|
||||
public String getNamespace() {
|
||||
return "urn:x-cast:org.mozilla.mirror";
|
||||
}
|
||||
|
||||
/*
|
||||
* Receive message from the receiver app
|
||||
*/
|
||||
@Override
|
||||
public void onMessageReceived(CastDevice castDevice, String namespace,
|
||||
String message) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("MediaPlayer:Response", message));
|
||||
}
|
||||
|
||||
public void sendMessage(String message) {
|
||||
if (apiClient != null && mMirrorChannel != null) {
|
||||
try {
|
||||
Cast.CastApi.sendMessage(apiClient, mMirrorChannel.getNamespace(), message)
|
||||
.setResultCallback(
|
||||
new ResultCallback<Status>() {
|
||||
@Override
|
||||
public void onResult(Status result) {
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Exception while sending message", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private class MirrorCallback implements ResultCallback<ApplicationConnectionResult> {
|
||||
|
||||
final EventCallback callback;
|
||||
MirrorCallback(final EventCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
|
||||
public void onResult(ApplicationConnectionResult result) {
|
||||
Status status = result.getStatus();
|
||||
if (status.isSuccess()) {
|
||||
ApplicationMetadata applicationMetadata = result.getApplicationMetadata();
|
||||
mSessionId = result.getSessionId();
|
||||
String applicationStatus = result.getApplicationStatus();
|
||||
boolean wasLaunched = result.getWasLaunched();
|
||||
mApplicationStarted = true;
|
||||
|
||||
// Create the custom message
|
||||
// channel
|
||||
mMirrorChannel = new MirrorChannel();
|
||||
try {
|
||||
Cast.CastApi.setMessageReceivedCallbacks(apiClient,
|
||||
mMirrorChannel
|
||||
.getNamespace(),
|
||||
mMirrorChannel);
|
||||
callback.sendSuccess(null);
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Exception while creating channel", e);
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Casting:Mirror", route.getId()));
|
||||
} else {
|
||||
callback.sendError(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void message(String msg, final EventCallback callback) {
|
||||
if (mMirrorChannel != null) {
|
||||
mMirrorChannel.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void mirror(final EventCallback callback) {
|
||||
final CastDevice device = CastDevice.getFromBundle(route.getExtras());
|
||||
Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(device, new Cast.Listener() {
|
||||
@Override
|
||||
public void onApplicationStatusChanged() { }
|
||||
|
||||
@Override
|
||||
public void onVolumeChanged() { }
|
||||
|
||||
@Override
|
||||
public void onApplicationDisconnected(int errorCode) { }
|
||||
});
|
||||
|
||||
apiClient = new GoogleApiClient.Builder(context)
|
||||
.addApi(Cast.API, apiOptionsBuilder.build())
|
||||
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
|
||||
@Override
|
||||
public void onConnected(Bundle connectionHint) {
|
||||
if (!apiClient.isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Launch the media player app and launch this url once its loaded
|
||||
try {
|
||||
Cast.CastApi.launchApplication(apiClient, MIRROR_RECIEVER_APP_ID, true)
|
||||
.setResultCallback(new MirrorCallback(callback));
|
||||
} catch (Exception e) {
|
||||
debug("Failed to launch application", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionSuspended(int cause) {
|
||||
debug("suspended");
|
||||
}
|
||||
}).build();
|
||||
|
||||
apiClient.connect();
|
||||
}
|
||||
|
||||
private static final String LOGTAG = "GeckoChromeCast";
|
||||
private void debug(String msg, Exception e) {
|
||||
if (SHOW_DEBUG) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
@ -19,6 +20,8 @@ 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 java.util.HashMap;
|
||||
|
||||
/* Wraper for different MediaRouter types supproted by Android. i.e. Chromecast, Miracast, etc. */
|
||||
@ -30,6 +33,8 @@ interface GeckoMediaPlayer {
|
||||
public void stop(EventCallback callback);
|
||||
public void start(EventCallback callback);
|
||||
public void end(EventCallback callback);
|
||||
public void mirror(EventCallback callback);
|
||||
public void message(String message, EventCallback callback);
|
||||
}
|
||||
|
||||
/* Manages a list of GeckoMediaPlayers methods (i.e. Chromecast/Miracast). Routes messages
|
||||
@ -74,14 +79,17 @@ class MediaPlayerManager implements NativeEventListener,
|
||||
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");
|
||||
mediaRouter = MediaRouter.getInstance(context);
|
||||
EventDispatcher.getInstance().registerGeckoThreadListener(this,
|
||||
"MediaPlayer:Load",
|
||||
"MediaPlayer:Start",
|
||||
"MediaPlayer:Stop",
|
||||
"MediaPlayer:Play",
|
||||
"MediaPlayer:Pause",
|
||||
"MediaPlayer:Get",
|
||||
"MediaPlayer:End",
|
||||
"MediaPlayer:Mirror",
|
||||
"MediaPlayer:Message");
|
||||
}
|
||||
|
||||
public static void onDestroy() {
|
||||
@ -89,13 +97,16 @@ class MediaPlayerManager implements NativeEventListener,
|
||||
return;
|
||||
}
|
||||
|
||||
EventDispatcher.getInstance().unregisterGeckoThreadListener(instance, "MediaPlayer:Load",
|
||||
"MediaPlayer:Start",
|
||||
"MediaPlayer:Stop",
|
||||
"MediaPlayer:Play",
|
||||
"MediaPlayer:Pause",
|
||||
"MediaPlayer:Get",
|
||||
"MediaPlayer:End");
|
||||
EventDispatcher.getInstance().unregisterGeckoThreadListener(instance,
|
||||
"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);
|
||||
@ -132,8 +143,10 @@ class MediaPlayerManager implements NativeEventListener,
|
||||
|
||||
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);
|
||||
Log.e(LOGTAG, "Couldn't find a display for this id: " + message.getString("id") + " for message: " + event);
|
||||
if (callback != null) {
|
||||
callback.sendError(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -147,6 +160,10 @@ class MediaPlayerManager implements NativeEventListener,
|
||||
display.pause(callback);
|
||||
} else if ("MediaPlayer:End".equals(event)) {
|
||||
display.end(callback);
|
||||
} else if ("MediaPlayer:Mirror".equals(event)) {
|
||||
display.mirror(callback);
|
||||
} else if ("MediaPlayer:Message".equals(event) && message.has("data")) {
|
||||
display.message(message.getString("data"), callback);
|
||||
} else if ("MediaPlayer:Load".equals(event)) {
|
||||
final String url = message.optString("source", "");
|
||||
final String type = message.optString("type", "video/mp4");
|
||||
@ -155,48 +172,49 @@ class MediaPlayerManager implements NativeEventListener,
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
private final MediaRouter.Callback callback =
|
||||
new MediaRouter.Callback() {
|
||||
@Override
|
||||
public void onRouteRemoved(MediaRouter router, RouteInfo route) {
|
||||
debug("onRouteRemoved: route=" + route);
|
||||
displays.remove(route.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
@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) {
|
||||
try {
|
||||
@ -221,6 +239,7 @@ class MediaPlayerManager implements NativeEventListener,
|
||||
MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder()
|
||||
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
|
||||
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
|
||||
.addControlCategory(CastMediaControlIntent.categoryForCast(ChromeCast.MIRROR_RECIEVER_APP_ID))
|
||||
.build();
|
||||
mediaRouter.addCallback(selectorBuilder, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
|
||||
}
|
||||
|
@ -65,11 +65,30 @@ var CastingApps = {
|
||||
Services.obs.addObserver(this, "Casting:Play", false);
|
||||
Services.obs.addObserver(this, "Casting:Pause", false);
|
||||
Services.obs.addObserver(this, "Casting:Stop", false);
|
||||
Services.obs.addObserver(this, "Casting:Mirror", false);
|
||||
|
||||
BrowserApp.deck.addEventListener("TabSelect", this, true);
|
||||
BrowserApp.deck.addEventListener("pageshow", this, true);
|
||||
BrowserApp.deck.addEventListener("playing", this, true);
|
||||
BrowserApp.deck.addEventListener("ended", this, true);
|
||||
|
||||
NativeWindow.menu.add(
|
||||
Strings.browser.GetStringFromName("casting.mirrorTab"),
|
||||
"drawable://casting",
|
||||
function() {
|
||||
function callbackFunc(aService) {
|
||||
let app = SimpleServiceDiscovery.findAppForService(aService);
|
||||
if (app)
|
||||
app.mirror(function() {
|
||||
});
|
||||
}
|
||||
|
||||
function filterFunc(aService) {
|
||||
Cu.reportError("testing: " + aService);
|
||||
return aService.mirror == true;
|
||||
}
|
||||
this.prompt(callbackFunc, filterFunc);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
uninit: function ca_uninit() {
|
||||
@ -81,6 +100,7 @@ var CastingApps = {
|
||||
Services.obs.removeObserver(this, "Casting:Play");
|
||||
Services.obs.removeObserver(this, "Casting:Pause");
|
||||
Services.obs.removeObserver(this, "Casting:Stop");
|
||||
Services.obs.removeObserver(this, "Casting:Mirror");
|
||||
|
||||
NativeWindow.contextmenus.remove(this._castMenuId);
|
||||
},
|
||||
@ -106,6 +126,12 @@ var CastingApps = {
|
||||
this.closeExternal();
|
||||
}
|
||||
break;
|
||||
case "Casting:Mirror":
|
||||
{
|
||||
Cu.import("resource://gre/modules/TabMirror.jsm");
|
||||
this.tabMirror = new TabMirror(aData, window);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -176,6 +176,7 @@ selectionHelper.textCopied=Text copied to clipboard
|
||||
|
||||
# Casting
|
||||
casting.prompt=Cast to Device
|
||||
casting.mirrorTab=Mirror Tab
|
||||
|
||||
# Context menu
|
||||
contextmenu.openInNewTab=Open Link in New Tab
|
||||
|
@ -13,7 +13,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
let log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "MediaPlayerApp");
|
||||
|
||||
// Helper function for sending commands to Java.
|
||||
// Helper function for sending commands to Java.
|
||||
function send(type, data, callback) {
|
||||
let msg = {
|
||||
type: type
|
||||
@ -52,6 +52,12 @@ MediaPlayerApp.prototype = {
|
||||
callback(new RemoteMedia(this.id, listener));
|
||||
}
|
||||
},
|
||||
|
||||
mirror: function mirror(callback) {
|
||||
send("MediaPlayer:Mirror", { id: this.id }, (result) => {
|
||||
if (callback) callback(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* RemoteMedia provides a proxy to a native media player session.
|
||||
|
@ -187,7 +187,8 @@ var SimpleServiceDiscovery = {
|
||||
friendlyName: display.friendlyName,
|
||||
uuid: display.uuid,
|
||||
manufacturer: display.manufacturer,
|
||||
modelName: display.modelName
|
||||
modelName: display.modelName,
|
||||
mirror: display.mirror
|
||||
};
|
||||
|
||||
this._addService(service);
|
||||
|
229
mobile/android/modules/TabMirror.jsm
Normal file
229
mobile/android/modules/TabMirror.jsm
Normal file
@ -0,0 +1,229 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let TabMirror = function(deviceId, window) {
|
||||
let out_queue = [];
|
||||
let in_queue = [];
|
||||
let DEBUG = false;
|
||||
|
||||
let log = Cu.import("resource://gre/modules/AndroidLog.jsm",
|
||||
{}).AndroidLog.d.bind(null, "TabMirror");
|
||||
|
||||
let RTCPeerConnection = window.mozRTCPeerConnection;
|
||||
let RTCSessionDescription = window.mozRTCSessionDescription;
|
||||
let RTCIceCandidate = window.mozRTCIceCandidate;
|
||||
let getUserMedia = window.navigator.mozGetUserMedia.bind(window.navigator);
|
||||
|
||||
Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
|
||||
in_queue.push(aData);
|
||||
}, "MediaPlayer:Response", false);
|
||||
|
||||
let poll_timeout = 1000; // ms
|
||||
|
||||
let audio_stream = undefined;
|
||||
let video_stream = undefined;
|
||||
let pc = undefined;
|
||||
|
||||
let poll_success = function(msg) {
|
||||
if (!msg) {
|
||||
poll_error();
|
||||
return;
|
||||
}
|
||||
|
||||
let js;
|
||||
try {
|
||||
js = JSON.parse(msg);
|
||||
} catch(ex) {
|
||||
log("ex: " + ex);
|
||||
}
|
||||
|
||||
let sdp = js.body;
|
||||
|
||||
if (sdp) {
|
||||
if (sdp.sdp) {
|
||||
if (sdp.type === "offer") {
|
||||
process_offer(sdp);
|
||||
} else if (sdp.type === "answer") {
|
||||
process_answer(sdp);
|
||||
}
|
||||
} else {
|
||||
process_ice_candidate(sdp);
|
||||
}
|
||||
}
|
||||
|
||||
window.setTimeout(poll, poll_timeout);
|
||||
};
|
||||
|
||||
let poll_error = function (msg) {
|
||||
window.setTimeout(poll, poll_timeout);
|
||||
};
|
||||
|
||||
let poll = function () {
|
||||
if (in_queue) {
|
||||
poll_success(in_queue.pop());
|
||||
}
|
||||
};
|
||||
|
||||
let failure = function(x) {
|
||||
log("ERROR: " + JSON.stringify(x));
|
||||
};
|
||||
|
||||
|
||||
// Signaling methods
|
||||
let send_sdpdescription= function(sdp) {
|
||||
let msg = {
|
||||
body: sdp
|
||||
};
|
||||
|
||||
sendMessage(JSON.stringify(msg));
|
||||
};
|
||||
|
||||
|
||||
let deobjify = function(x) {
|
||||
return JSON.parse(JSON.stringify(x));
|
||||
};
|
||||
|
||||
|
||||
let process_offer = function(sdp) {
|
||||
pc.setRemoteDescription(new RTCSessionDescription(sdp),
|
||||
set_remote_offer_success, failure);
|
||||
};
|
||||
|
||||
let process_answer = function(sdp) {
|
||||
pc.setRemoteDescription(new RTCSessionDescription(sdp),
|
||||
set_remote_answer_success, failure);
|
||||
};
|
||||
|
||||
let process_ice_candidate = function(msg) {
|
||||
pc.addIceCandidate(new RTCIceCandidate(msg));
|
||||
};
|
||||
|
||||
let set_remote_offer_success = function() {
|
||||
pc.createAnswer(create_answer_success, failure);
|
||||
};
|
||||
|
||||
let set_remote_answer_success= function() {
|
||||
};
|
||||
|
||||
let set_local_success_offer = function(sdp) {
|
||||
send_sdpdescription(sdp);
|
||||
};
|
||||
|
||||
let set_local_success_answer = function(sdp) {
|
||||
send_sdpdescription(sdp);
|
||||
};
|
||||
|
||||
let filter_nonrelay_candidates = function(sdp) {
|
||||
let lines = sdp.sdp.split("\r\n");
|
||||
let lines2 = lines.filter(function(x) {
|
||||
if (!/candidate/.exec(x))
|
||||
return true;
|
||||
if (/relay/.exec(x))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
sdp.sdp = lines2.join("\r\n");
|
||||
};
|
||||
|
||||
let create_offer_success = function(sdp) {
|
||||
pc.setLocalDescription(sdp,
|
||||
function() {
|
||||
set_local_success_offer(sdp);
|
||||
},
|
||||
failure);
|
||||
};
|
||||
|
||||
let create_answer_success = function(sdp) {
|
||||
pc.setLocalDescription(sdp,
|
||||
function() {
|
||||
set_local_success_answer(sdp);
|
||||
},
|
||||
failure);
|
||||
};
|
||||
|
||||
let on_ice_candidate = function (candidate) {
|
||||
send_sdpdescription(candidate.candidate);
|
||||
};
|
||||
|
||||
let ready = function() {
|
||||
pc.createOffer(create_offer_success, failure);
|
||||
poll();
|
||||
};
|
||||
|
||||
|
||||
let config = {
|
||||
iceServers: [{ "url": "stun:stun.services.mozilla.com" }]
|
||||
};
|
||||
|
||||
let pc = new RTCPeerConnection(config, {});
|
||||
|
||||
if (!pc) {
|
||||
log("Failure creating Webrtc object");
|
||||
return;
|
||||
}
|
||||
|
||||
pc.onicecandidate = on_ice_candidate;
|
||||
|
||||
let windowId = window.BrowserApp.selectedBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||
let viewport = window.BrowserApp.selectedTab.getViewport();
|
||||
let maxWidth = Math.max(viewport.cssWidth, viewport.width);
|
||||
let maxHeight = Math.max(viewport.cssHeight, viewport.height);
|
||||
let minRatio = Math.sqrt((maxWidth * maxHeight) / (640 * 480));
|
||||
|
||||
let screenWidth = 640;
|
||||
let screenHeight = 480;
|
||||
let videoWidth = 0;
|
||||
let videoHeight = 0;
|
||||
if (screenWidth/screenHeight > maxWidth / maxHeight) {
|
||||
videoWidth = screenWidth;
|
||||
videoHeight = Math.ceil(videoWidth * maxHeight / maxWidth);
|
||||
} else {
|
||||
videoHeight = screenHeight;
|
||||
videoWidth = Math.ceil(videoHeight * maxWidth / maxHeight);
|
||||
}
|
||||
|
||||
let constraints = {
|
||||
video: {
|
||||
mediaSource: "browser",
|
||||
advanced: [
|
||||
{ width: { min: videoWidth, max: videoWidth },
|
||||
height: { min: videoHeight, max: videoHeight }
|
||||
},
|
||||
{ aspectRatio: maxWidth / maxHeight }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let gUM_success = function(stream){
|
||||
pc.addStream(stream);
|
||||
ready();
|
||||
};
|
||||
|
||||
let gUM_failure = function() {
|
||||
log("Could not get video stream");
|
||||
};
|
||||
|
||||
getUserMedia( constraints, gUM_success, gUM_failure);
|
||||
|
||||
function sendMessage(msg) {
|
||||
let obj = {
|
||||
type: "MediaPlayer:Message",
|
||||
id: deviceId,
|
||||
data: msg
|
||||
};
|
||||
|
||||
if (deviceId) {
|
||||
Services.androidBridge.handleGeckoMessage(obj);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["TabMirror"];
|
@ -24,6 +24,7 @@ EXTRA_JS_MODULES += [
|
||||
'SharedPreferences.jsm',
|
||||
'SimpleServiceDiscovery.jsm',
|
||||
'SSLExceptions.jsm',
|
||||
'TabMirror.jsm',
|
||||
'WebappManagerWorker.js',
|
||||
]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user