Merge pull request #678 from ritzalam/reconnect-to-bbb-video

Reconnect to bbb video
This commit is contained in:
Richard Alam 2015-07-02 10:46:30 -04:00
commit 60f9dda8e8
5 changed files with 84 additions and 23 deletions

View File

@ -178,13 +178,13 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
String streamId = stream.getPublishedName(); String streamId = stream.getPublishedName();
publisher.userSharedWebcamMessage(meetingId, userId, streamId); publisher.userSharedWebcamMessage(meetingId, userId, streamId);
VideoStreamListener listener = new VideoStreamListener(conn.getScope(), stream, recordVideoStream);
listener.setEventRecordingService(recordingService);
stream.addStreamListener(listener);
streamListeners.put(conn.getScope().getName() + "-" + stream.getPublishedName(), listener);
if (recordVideoStream) { if (recordVideoStream) {
recordStream(stream); recordStream(stream);
VideoStreamListener listener = new VideoStreamListener();
listener.setEventRecordingService(recordingService);
stream.addStreamListener(listener);
streamListeners.put(conn.getScope().getName() + "-" + stream.getPublishedName(), listener);
} }
} }
@ -212,13 +212,13 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
publisher.userUnshareWebcamRequestMessage(meetingId, userId, streamId); publisher.userUnshareWebcamRequestMessage(meetingId, userId, streamId);
if (recordVideoStream) {
IStreamListener listener = streamListeners.remove(scopeName + "-" + stream.getPublishedName()); IStreamListener listener = streamListeners.remove(scopeName + "-" + stream.getPublishedName());
if (listener != null) { if (listener != null) {
stream.removeStreamListener(listener); stream.removeStreamListener(listener);
} }
if (recordVideoStream) {
long publishDuration = (System.currentTimeMillis() - stream.getCreationTime()) / 1000; long publishDuration = (System.currentTimeMillis() - stream.getCreationTime()) / 1000;
log.info("streamBroadcastClose " + stream.getPublishedName() + " " + System.currentTimeMillis() + " " + scopeName); log.info("streamBroadcastClose " + stream.getPublishedName() + " " + System.currentTimeMillis() + " " + scopeName);
Map<String, String> event = new HashMap<String, String>(); Map<String, String> event = new HashMap<String, String>();

View File

@ -21,14 +21,20 @@ package org.bigbluebutton.app.video;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.buffer.IoBuffer;
import org.red5.server.api.IConnection; import org.red5.server.api.IConnection;
import org.red5.server.api.Red5; import org.red5.server.api.Red5;
import org.red5.server.api.scheduling.IScheduledJob;
import org.red5.server.api.scheduling.ISchedulingService;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.stream.IBroadcastStream; import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IStreamListener; import org.red5.server.api.stream.IStreamListener;
import org.red5.server.api.stream.IStreamPacket; import org.red5.server.api.stream.IStreamPacket;
import org.red5.server.net.rtmp.event.VideoData; import org.red5.server.net.rtmp.event.VideoData;
import org.red5.server.scheduling.QuartzSchedulingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.red5.logging.Red5LoggerFactory;
/** /**
* Class to listen for the first video packet of the webcam. * Class to listen for the first video packet of the webcam.
@ -46,12 +52,44 @@ import org.red5.server.net.rtmp.event.VideoData;
* *
*/ */
public class VideoStreamListener implements IStreamListener { public class VideoStreamListener implements IStreamListener {
private static final Logger log = Red5LoggerFactory.getLogger(VideoStreamListener.class, "video");
private EventRecordingService recordingService; private EventRecordingService recordingService;
private volatile boolean firstPacketReceived = false; private volatile boolean firstPacketReceived = false;
private Long genTimestamp() { // Maximum time between video packets
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); private int videoTimeout = 10000;
}
// Last time video was received, not video timestamp
private long lastVideoTime;
// Stream being observed
private IBroadcastStream stream;
// if this stream is recorded or not
private boolean record;
// Scheduler
private QuartzSchedulingService scheduler;
// Event queue worker job name
private String timeoutJobName;
private IScope scope;
public VideoStreamListener(IScope scope, IBroadcastStream stream, Boolean record) {
this.scope = scope;
this.stream = stream;
this.record = record;
// get the scheduler
scheduler = (QuartzSchedulingService) scope.getParent().getContext().getBean(QuartzSchedulingService.BEAN_NAME);
}
private Long genTimestamp() {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
}
@Override @Override
public void packetReceived(IBroadcastStream stream, IStreamPacket packet) { public void packetReceived(IBroadcastStream stream, IStreamPacket packet) {
@ -64,17 +102,25 @@ public class VideoStreamListener implements IStreamListener {
} }
if (packet instanceof VideoData) { if (packet instanceof VideoData) {
// keep track of last time video was received
lastVideoTime = System.currentTimeMillis();
if (! firstPacketReceived) { if (! firstPacketReceived) {
firstPacketReceived = true; firstPacketReceived = true;
IConnection conn = Red5.getConnectionLocal(); // start the worker
Map<String, String> event = new HashMap<String, String>(); timeoutJobName = scheduler.addScheduledJob(videoTimeout, new TimeoutJob());
event.put("module", "WEBCAM");
event.put("timestamp", genTimestamp().toString());
event.put("meetingId", conn.getScope().getName());
event.put("stream", stream.getPublishedName());
event.put("eventName", "StartWebcamShareEvent");
recordingService.record(conn.getScope().getName(), event); if (record) {
IConnection conn = Red5.getConnectionLocal();
Map<String, String> event = new HashMap<String, String>();
event.put("module", "WEBCAM");
event.put("timestamp", genTimestamp().toString());
event.put("meetingId", scope.getName());
event.put("stream", stream.getPublishedName());
event.put("eventName", "StartWebcamShareEvent");
recordingService.record(conn.getScope().getName(), event);
}
} }
} }
} }
@ -83,4 +129,18 @@ public class VideoStreamListener implements IStreamListener {
recordingService = s; recordingService = s;
} }
private class TimeoutJob implements IScheduledJob {
public void execute(ISchedulingService service) {
if ((System.currentTimeMillis() - lastVideoTime) > videoTimeout) {
log.warn("No data received for stream[{}] in the last few seconds. Close stream.", stream.getPublishedName());
// remove the scheduled job
scheduler.removeScheduledJob(timeoutJobName);
// stop / clean up
stream.stop();
}
}
}
} }

View File

@ -548,7 +548,7 @@ package org.bigbluebutton.modules.users.services
UserManager.getInstance().getConference().addUser(user); UserManager.getInstance().getConference().addUser(user);
if (joinedUser.hasStream) { if (joinedUser.hasStream) {
var streams:Array = joinedUser.webcamStream.split("|"); var streams:Array = joinedUser.webcamStream;
for each(var stream:String in streams) { for each(var stream:String in streams) {
UserManager.getInstance().getConference().sharedWebcam(user.userID, stream); UserManager.getInstance().getConference().sharedWebcam(user.userID, stream);
} }

View File

@ -464,6 +464,7 @@ package org.bigbluebutton.modules.videoconf.maps
stopAllBroadcasting(); stopAllBroadcasting();
trace("VideoEventMapDelegate:: Closing all webcam windows."); trace("VideoEventMapDelegate:: Closing all webcam windows.");
closeAllWindows() closeAllWindows()
openWebcamWindows();
} else { } else {
addToolbarButton(); addToolbarButton();
openWebcamWindows(); openWebcamWindows();

View File

@ -66,7 +66,7 @@ package org.bigbluebutton.modules.videoconf.views
protected function getVideoProfile(stream:String):VideoProfile { protected function getVideoProfile(stream:String):VideoProfile {
trace("Parsing stream name [" + stream + "]"); trace("Parsing stream name [" + stream + "]");
var pattern:RegExp = new RegExp("([A-Za-z0-9]+)-([A-Za-z0-9]+)-\\d+", ""); var pattern:RegExp = new RegExp("([A-Za-z0-9]+)-([A-Za-z0-9_]+)-\\d+", "");
if (pattern.test(stream)) { if (pattern.test(stream)) {
trace("The stream name is well formatted"); trace("The stream name is well formatted");
trace("Video profile resolution is [" + pattern.exec(stream)[1] + "]"); trace("Video profile resolution is [" + pattern.exec(stream)[1] + "]");