/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.deskshare.client.net;
import java.awt.Point;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import org.bigbluebutton.deskshare.client.ExitCode;
import org.bigbluebutton.deskshare.common.CaptureEvents;
import org.bigbluebutton.deskshare.common.Dimension;
import com.myjavatools.web.ClientHttpRequest;
import java.util.Date;
import java.text.SimpleDateFormat;
public class NetworkHttpStreamSender implements Runnable {
private static final String SEQ_NUM = "sequenceNumber";
private static final String ROOM = "room";
private static final String BLOCK = "blockInfo";
private static final String EVENT = "event";
private static final String SCREEN = "screenInfo";
private static final String POSITION = "position";
private static final String KEYFRAME = "keyframe";
private static final String BLOCKDATA = "blockdata";
private static final String MOUSEX = "mousex";
private static final String MOUSEY = "mousey";
private static final String BLOCKGROUP = "blockgroup";
// The previously sent location of the mouse pointer
private static Point previousMouseLocation = new Point(0,0);
private static final String USE_SVC2 = "svc2";
private String host = "localhost";
private static final String SCREEN_CAPTURE__URL = "/deskshare/tunnel/screenCapture";
private URL url;
private URLConnection conn;
private String room;
private Dimension screenDim;
private Dimension blockDim;
private boolean useSVC2;
private final NextBlockRetriever retriever;
private volatile boolean processBlocks = false;
private final int id;
private NetworkStreamListener listener;
private final SequenceNumberGenerator seqNumGenerator;
public NetworkHttpStreamSender(int id, NextBlockRetriever retriever, String room,
Dimension screenDim, Dimension blockDim, SequenceNumberGenerator seqNumGenerator, boolean useSVC2) {
this.id = id;
this.retriever = retriever;
this.room = room;
this.screenDim = screenDim;
this.blockDim = blockDim;
this.seqNumGenerator = seqNumGenerator;
this.useSVC2 = useSVC2;
}
public void addListener(NetworkStreamListener listener) {
this.listener = listener;
}
private void notifyNetworkStreamListener(ExitCode reason) {
if (listener != null) listener.networkException(id,reason);
}
public void connect(String host) throws ConnectionException {
this.host = host;
System.out.println("Starting NetworkHttpStreamSender to " + host);
openConnection();
}
private void openConnection() throws ConnectionException {
/**
* Need to re-establish connection each time, otherwise,
* we get java.net.ProtocolException: Cannot write output after reading input.
*
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4382944
*
*/
long start = System.currentTimeMillis();
try {
url = new URL("http://" + host + SCREEN_CAPTURE__URL);
conn = url.openConnection();
} catch (MalformedURLException e) {
e.printStackTrace();
throw new ConnectionException("MalformedURLException " + url.toString());
} catch (IOException e) {
e.printStackTrace();
throw new ConnectionException("IOException while connecting to " + url.toString());
}
long end = System.currentTimeMillis();
System.out.println("Http[" + id + "] Open connection took [" + (end-start) + " ms]");
}
public void sendStartStreamMessage() {
try {
System.out.println("Http[" + id + "] Open connection. In sendStartStreamMessage");
openConnection();
sendCaptureStartEvent(screenDim, blockDim);
} catch (ConnectionException e) {
e.printStackTrace();
notifyNetworkStreamListener(ExitCode.DESKSHARE_SERVICE_UNAVAILABLE);
}
}
private void sendCaptureStartEvent(Dimension screen, Dimension block) throws ConnectionException {
ClientHttpRequest chr;
try {
chr = new ClientHttpRequest(conn);
chr.setParameter(ROOM, room);
chr.setParameter(SEQ_NUM, seqNumGenerator.getNext());
String screenInfo = Integer.toString(screen.getWidth()) + "x" + Integer.toString(screen.getHeight());
chr.setParameter(SCREEN, screenInfo);
String blockInfo = Integer.toString(block.getWidth()) + "x" + Integer.toString(block.getHeight());
chr.setParameter(BLOCK, blockInfo);
chr.setParameter(EVENT, CaptureEvents.CAPTURE_START.getEvent());
chr.setParameter(USE_SVC2, Boolean.toString(useSVC2));
chr.post();
} catch (IOException e) {
e.printStackTrace();
throw new ConnectionException("IOException while sending capture start event.");
}
}
public void disconnect() throws ConnectionException {
try {
System.out.println("Http[" + id + "] Open connection. In disconnect");
openConnection();
sendCaptureEndEvent();
} catch (ConnectionException e) {
e.printStackTrace();
notifyNetworkStreamListener(ExitCode.DESKSHARE_SERVICE_UNAVAILABLE);
throw e;
} finally {
processBlocks = false;
}
}
private void sendCaptureEndEvent() throws ConnectionException {
ClientHttpRequest chr;
try {
chr = new ClientHttpRequest(conn);
chr.setParameter(ROOM, room);
chr.setParameter(SEQ_NUM, seqNumGenerator.getNext());
chr.setParameter(EVENT, CaptureEvents.CAPTURE_END.getEvent());
chr.post();
} catch (IOException e) {
e.printStackTrace();
throw new ConnectionException("IOException while sending capture end event.");
}
}
private void processNextMessageToSend(Message message) {
if (message.getMessageType() == Message.MessageType.BLOCK) {
long start = System.currentTimeMillis();
Integer[] changedBlocks = ((BlockMessage) message).getBlocks();
String blockSize = "Http[" + id + "] Block length [";
String encodeTime = "Http[" + id + "]Encode times [";
long encStart = 0;
long encEnd = 0;
int totalBytes = 0;
long totalMillis = 0;
ClientHttpRequest chr;
try {
// Open a connection to the web server and create a request that has
// the room and event type.
System.out.println(getTimeStamp() + " Http[" + id + "] Open connection. In sendBlockData");
openConnection();
chr = new ClientHttpRequest(conn);
chr.setParameter(ROOM, room);
chr.setParameter(EVENT, CaptureEvents.CAPTURE_UPDATE.getEvent());
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// For each changed block, append an uploaded-file entry to the form data.
// The file name contains the block info. The file name has this format:
// "blockgroup_<seqnum>_<position>". The original filename is just set to
// "block<i>".
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
for (int i = 0; i < changedBlocks.length; i++)
{ // append each changed-block to the form
EncodedBlockData block = retriever.getBlockToSend((Integer) changedBlocks[i]);
String changed_blocks_upload_filename =
BLOCKGROUP + "_" + seqNumGenerator.getNext() + "_" + block.getPosition();
chr.setParameter(changed_blocks_upload_filename, "block"+i, new ByteArrayInputStream(block.getVideoData()));
} // append each changed-block to the form
// Post the multi-part form to the server
chr.post();
HttpURLConnection httpConnection = (HttpURLConnection) chr.connection;
int status = httpConnection.getResponseCode();
System.out.println("******* POST status = [" + status + "] ***************");
System.out.println(blockSize + "] total=" + totalBytes + " bytes");
System.out.println(encodeTime + "] total=" + totalMillis + " ms");
for (int i = 0; i < changedBlocks.length; i++) {
retriever.blockSent((Integer) changedBlocks[i]);
}
long end = System.currentTimeMillis();
System.out.println("[HTTP " + id + "] Sending " + changedBlocks.length + " blocks took " + (end - start) + " millis");
} catch (IOException e) {
e.printStackTrace();
} catch (ConnectionException e) {
System.out.println("ERROR: Failed to send block data.");
}
}
else if (message.getMessageType() == Message.MessageType.CURSOR)
{
CursorMessage msg = (CursorMessage) message;
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// If the mouse has changed location from the previous time sent,
// then send its new location.
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
if (!msg.getMouseLocation().equals(previousMouseLocation))
{
System.out.println("SEND MOUSE: old=" + previousMouseLocation + " new=" + msg.getMouseLocation());
previousMouseLocation = msg.getMouseLocation();
sendCursor(previousMouseLocation, msg.getRoom());
}
}
}
private void processNextMessageToSend_OLD(Message message) {
if (message.getMessageType() == Message.MessageType.BLOCK) {
long start = System.currentTimeMillis();
Integer[] changedBlocks = ((BlockMessage)message).getBlocks();
String blockSize = "Http[" + id + "] Block length [";
String encodeTime = "Http[" + id + "]Encode times [";
long encStart = 0;
long encEnd = 0;
int totalBytes = 0;
long totalMillis = 0;
for (int i = 0; i < changedBlocks.length; i++) {
encStart = System.currentTimeMillis();
EncodedBlockData block = retriever.getBlockToSend((Integer)changedBlocks[i]);
totalBytes += block.getVideoData().length;
blockSize += block.getVideoData().length + ",";
encEnd = System.currentTimeMillis();
totalMillis += (encEnd - encStart);
encodeTime += (encEnd - encStart) + ",";
BlockVideoData bv = new BlockVideoData(room, block.getPosition(), block.getVideoData(), false /* should remove later */);
sendBlockData(bv);
}
System.out.println(blockSize + "] total=" + totalBytes + " bytes");
System.out.println(encodeTime + "] total=" + totalMillis + " ms");
for (int i = 0; i< changedBlocks.length; i++) {
retriever.blockSent((Integer)changedBlocks[i]);
}
long end = System.currentTimeMillis();
System.out.println("[HTTP " + id + "] Sending " + changedBlocks.length + " blocks took " + (end - start) + " millis");
} else if (message.getMessageType() == Message.MessageType.CURSOR) {
CursorMessage msg = (CursorMessage)message;
sendCursor(msg.getMouseLocation(), msg.getRoom());
}
}
// NEW
public void stopProcessingBlocks() { processBlocks = false; }
public void run() {
processBlocks = true;
while (processBlocks) {
Message message;
try {
message = retriever.getNextMessageToSend();
processNextMessageToSend(message);
} catch (InterruptedException e) {
e.printStackTrace();
notifyNetworkStreamListener(ExitCode.DESKSHARE_SERVICE_UNAVAILABLE);
processBlocks = false;
}
}
}
private void sendCursor(Point mouseLoc, String room) {
ClientHttpRequest chr;
try {
System.out.println("Http[" + id + "] Open connection. In sendCursor");
openConnection();
chr = new ClientHttpRequest(conn);
chr.setParameter(ROOM, room);
chr.setParameter(SEQ_NUM, seqNumGenerator.getNext());
chr.setParameter(EVENT, CaptureEvents.MOUSE_LOCATION_EVENT.getEvent());
chr.setParameter(MOUSEX, mouseLoc.x);
chr.setParameter(MOUSEY, mouseLoc.y);
chr.post();
} catch (IOException e) {
e.printStackTrace();
} catch (ConnectionException e) {
System.out.println("ERROR: Failed to send block data.");
}
}
private String getTimeStamp()
{
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS");//dd/MM/yyyy
Date now = new Date();
String strDate = sdfDate.format(now);
return strDate;
}
private void sendBlockData(BlockVideoData blockData) {
long start = System.currentTimeMillis();
ClientHttpRequest chr;
try {
System.out.println(getTimeStamp()+ " Http[" + id + "] Open connection. In sendBlockData");
openConnection();
chr = new ClientHttpRequest(conn);
chr.setParameter(ROOM, blockData.getRoom());
chr.setParameter(SEQ_NUM, seqNumGenerator.getNext());
chr.setParameter(POSITION, blockData.getPosition());
chr.setParameter(KEYFRAME, blockData.isKeyFrame());
chr.setParameter(EVENT, CaptureEvents.CAPTURE_UPDATE.getEvent());
ByteArrayInputStream block = new ByteArrayInputStream(blockData.getVideoData());
chr.setParameter(BLOCKDATA, "block", block);
chr.post();
// try {
// Thread.sleep(1000);
// }
// catch (InterruptedException e)
// { }
} catch (IOException e) {
e.printStackTrace();
} catch (ConnectionException e) {
System.out.println("ERROR: Failed to send block data.");
}
long end = System.currentTimeMillis();
System.out.println(getTimeStamp() + " [HTTP " + id + "] Sending " + blockData.getVideoData().length + " bytes took " + (end - start) + " ms");
}
}