/* | |
* 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; | |
} | |
} |