blob: 2aeff96a138f7ccfd014366031d36e227fb80a59 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License") + you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openmeetings.screen.webstart;
import static org.apache.openmeetings.screen.webstart.gui.ScreenDimensions.quality;
import static org.apache.openmeetings.screen.webstart.gui.ScreenDimensions.spinnerHeight;
import static org.apache.openmeetings.screen.webstart.gui.ScreenDimensions.spinnerWidth;
import static org.apache.openmeetings.screen.webstart.gui.ScreenDimensions.spinnerX;
import static org.apache.openmeetings.screen.webstart.gui.ScreenDimensions.spinnerY;
import static org.slf4j.LoggerFactory.getLogger;
import java.awt.Rectangle;
import java.awt.Robot;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.mina.core.buffer.IoBuffer;
import org.red5.server.net.rtmp.event.VideoData;
import org.red5.server.stream.message.RTMPMessage;
import org.slf4j.Logger;
final class CaptureScreen extends Thread {
private static final Logger log = getLogger(CaptureScreen.class);
private static final int NANO_MULTIPLIER = 1000 * 1000;
private CoreScreenShare core;
private int timeBetweenFrames;
private volatile int timestamp = 0;
private volatile boolean active = true;
private IScreenEncoder se;
private IScreenShare client;
private IoBuffer buffer;
private String host = null;
private String app = null;
private int port = -1;
private int streamId;
private boolean startPublish = false;
private boolean sendCursor = false;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(20);
private final ScheduledExecutorService cursorScheduler = Executors.newScheduledThreadPool(1);
public CaptureScreen(CoreScreenShare coreScreenShare, IScreenShare client, String host, String app, int port) {
core = coreScreenShare;
this.client = client;
this.host = host;
this.app = app;
this.port = port;
switch (quality) {
case VeryHigh:
timeBetweenFrames = 50;
break;
case High:
timeBetweenFrames = 200;
break;
case Low:
case Medium:
default:
timeBetweenFrames = 500;
break;
}
se = new ScreenV1Encoder(); //NOTE get image should be changed in the code below
}
public void release() {
active = false;
timestamp = 0;
try {
scheduler.shutdownNow();
} catch (Exception e) {
//no-op
}
try {
cursorScheduler.shutdownNow();
} catch (Exception e) {
//no-op
}
}
public void resetBuffer() {
se.reset();
}
public void run() {
try {
while (active && !core.isReadyToRecord()) {
Thread.sleep(60);
}
scheduler.scheduleWithFixedDelay(new Runnable() {
Robot robot = new Robot();
Rectangle screen = new Rectangle(spinnerX, spinnerY, spinnerWidth, spinnerHeight);
int[][] image = null;
public void run() {
long start = System.currentTimeMillis();
image = ScreenV1Encoder.getImage(screen, robot);
if (log.isTraceEnabled()) {
log.trace(String.format("Image was captured in %s ms", System.currentTimeMillis() - start));
}
start = System.currentTimeMillis();
try {
byte[] data = se.encode(screen, image);
if (log.isTraceEnabled()) {
log.trace(String.format("Image was encoded in %s ms", System.currentTimeMillis() - start));
}
timestamp += timeBetweenFrames;
pushVideo(data, timestamp);
} catch (IOException e) {
log.error("Error while encoding/sending: ", e);
}
}
}, 0, timeBetweenFrames * NANO_MULTIPLIER, TimeUnit.NANOSECONDS);
if (sendCursor) {
cursorScheduler.scheduleWithFixedDelay(new Runnable() {
public void run() {
core.sendCursorStatus();
}
}, 0, timeBetweenFrames * NANO_MULTIPLIER, TimeUnit.NANOSECONDS);
}
} catch (Exception e) {
log.error("Error while running: ", e);
}
}
/*
private void pushAudio(byte[] audio, long ts) {
if (startPublish) {
buffer.put((byte) 6);
buffer.put(audio);
buffer.flip();
// I can stream audio
//packets successfully using linear PCM at 11025Hz. For those packets I
//push one byte (0x06) which specifies the format of audio data in a
//ByteBuffer, and then real audio data:
RTMPMessage rtmpMsg = RTMPMessage.build(new AudioData(buffer), (int) ts);
client.publishStreamData(streamId, rtmpMsg);
}
}
*/
private void pushVideo(byte[] video, int ts) throws IOException {
if (startPublish) {
if (buffer == null || (buffer.capacity() < video.length && !buffer.isAutoExpand())) {
buffer = IoBuffer.allocate(video.length);
buffer.setAutoExpand(true);
}
buffer.clear();
buffer.put(video);
buffer.flip();
log.trace("Video frame sent :: " + ts);
RTMPMessage rtmpMsg = RTMPMessage.build(new VideoData(buffer), ts);
client.publishStreamData(streamId, rtmpMsg);
}
}
public String getHost() {
return host;
}
public String getApp() {
return app;
}
public int getPort() {
return port;
}
public int getStreamId() {
return streamId;
}
public void setStreamId(int streamId) {
this.streamId = streamId;
}
public void setStartPublish(boolean startPublish) {
this.startPublish = startPublish;
}
public void setSendCursor(boolean sendCursor) {
this.sendCursor = sendCursor;
}
}